Daily LeetCode – day0085 1235. Maximum Profit in Job Scheduling

import java.util.Arrays;

// 1235. Maximum Profit in Job Scheduling
class Solution {
    public int jobScheduling(int[] startTime, int[] endTime, int[] profit) {
        int n = startTime.length;
        int[][] jobs = new int[n + 1][];
        jobs[0] = new int[]{0, 0, 0};
        for (int i = 0; i < n; ++i) {
            jobs[i + 1] = new int[]{startTime[i], endTime[i], profit[i]};
        }
        Arrays.sort(jobs, (j1, j2) -> j1[1] - j2[1]);
        int[] dp = new int[n + 1];
        for (int i = 1; i <= n; ++i) {
            int j = binarySearch(i, jobs[i][0], jobs);
            dp[i] = Math.max(dp[i - 1], dp[j] + jobs[i][2]);
        }
        return dp[n];
    }

    int binarySearch(int rightest, int latestEndTime, int[][] jobs) {
        int left = 0;
        int right = rightest;
        while (left < right) {
            int mid = left + (right - left) / 2;
            if (jobs[mid][1] <= latestEndTime) {
                left = mid + 1;
            } else {
                right = mid;
            }
        }
        return left - 1;
    }
}
学习笔记:
今天是一道困难题,需要用到动态规划 + 二分搜素。
今天做到二分搜索的时候遇到了疑惑,刚好今天六点多就结束加班了,就学习了一波。
二分搜索分为三种,搜索一个数找不到就返回-1的,搜索左边界的,搜索右边界的。

先看循环条件:
搜索一个数的话,left <= right
搜索边界的话,left < right

核心情况在于nums[mid] == target
搜索一个数的话,返回mid
搜索左边界,right = mid
搜索右边界,left = mid + 1

返回值要注意的就是
搜索左边界,返回left或right
搜索右边界,返回left - 1或right - 1
// 搜索一个数

int binarySearch(int[] nums, int target) {
    int left = 0; 
    int right = nums.length - 1; // 注意
 
    while(left <= right) {
        int mid = (right + left) / 2;
        if(nums[mid] == target)
            return mid; 
        else if (nums[mid] < target)
            left = mid + 1; // 注意
        else if (nums[mid] > target)
            right = mid - 1; // 注意
        }
    return -1;
}

// 搜索左边界

int left_bound(int[] nums, int target) {
    if (nums.length == 0) return -1;
    int left = 0;
    int right = nums.length; // 注意
    
    while (left < right) { // 注意
        int mid = (left + right) / 2;
        if (nums[mid] == target) {
            right = mid;
        } else if (nums[mid] < target) {
            left = mid + 1;
        } else if (nums[mid] > target) {
            right = mid; // 注意
        }
    }
    return left;
}


// 搜索右边界

int right_bound(int[] nums, int target) {
    if (nums.length == 0) return -1;
    int left = 0, right = nums.length;
    
    while (left < right) {
        int mid = (left + right) / 2;
        if (nums[mid] == target) {
            left = mid + 1; // 注意
        } else if (nums[mid] < target) {
            left = mid + 1;
        } else if (nums[mid] > target) {
            right = mid;
        }
    }
    return left - 1; // 注意
}
发表在 每日LeetCode | 留下评论

Daily LeetCode – day0084 0901. Online Stock Span

import java.util.ArrayList;
import java.util.List;

// 0901. Online Stock Span
class StockSpanner {
    List<Stock> stockList;
    int current;

    public StockSpanner() {
        stockList = new ArrayList<>();
        current = 0;
    }

    public int next(int price) {
        int check = current - 1;
        while (check >= 0) {
            Stock checkStock = stockList.get(check);
            if (price >= checkStock.price) {
                check -= checkStock.span;
            } else {
                break;
            }
        }
        int ret = current - check;
        stockList.add(new Stock(price, ret));
        ++current;
        return ret;
    }
}

class Stock {
    int price;
    int span;

    public Stock(int price, int span) {
        this.price = price;
        this.span = span;
    }
}
学习笔记:
这是一道单调栈的题目,要考虑单调性。
一个个往前找就太不划算了,需要根据上一个的单调性跳着找效率才高。
不过我也不清楚栈的写法,我的写法是比较朴素的面向对象写法。
最近天天加班,属实没有什么多余时间研究。
发表在 每日LeetCode | 留下评论

Daily LeetCode – day0083 0779. K-th Symbol in Grammar

// 0779. K-th Symbol in Grammar
class Solution {
    public int kthGrammar(int n, int k) {
        if (n == 1) return 0;
        if (k <= 1 << n - 2) return kthGrammar(n - 1, k);
        else return kthGrammar(n - 1, k - (1 << n - 2)) ^ 1;
    }
}
学习笔记:
这是一道找规律的题目。
0
01
0110
01101001
0110100110010110
后半段都是前半段取反后平移到后面来的。
每一行的前半段都和上面的一样。
我们要找第5行第11个,怎么办呢?
第5行有16个,后8个是前8个取反来的。第5行第11个就是第5行第3个取反。
那么第5行第3个是什么呢?其实就是第4行第3个。
那么第4行第3个是什么呢?其实就是第3行第3个。
那么第3行第3个是什么呢?其实就是第3行第1个取反。
那么第3行第1个是什么呢?其实就是第2行第1个。
那么第2行第1个是什么呢?其实就是第1行第1个也即是0。
所以就有了推导的公式,一旦发现处于后半段就需要取反。
发表在 每日LeetCode | 留下评论

Daily LeetCode – day0082 1700. Number of Students Unable to Eat Lunch

// 1700. Number of Students Unable to Eat Lunch
class Solution {
    public int countStudents(int[] students, int[] sandwiches) {
        int circleStudents = 0;
        int squareStudents = 0;
        for (int student : students) {
            if (student == 0) ++circleStudents;
            else ++squareStudents;
        }
        for (int sandwich : sandwiches) {
            if (sandwich == 0) {
                if (circleStudents > 0) --circleStudents;
                else return squareStudents;
            } else {
                if (squareStudents > 0) --squareStudents;
                else return circleStudents;
            }
        }
        return 0;
    }
}
学习笔记:
这是一道简单题,虽说看上去说是队列和栈,实际上根本都用不到。
就只需要统计一下学生,反正学生不喜欢会继续排的。
一直到栈顶的三文治没人拿为止,那剩下的那一类学生都要挨饿。
发表在 每日LeetCode | 留下评论

Daily LeetCode – day0081 0902. Numbers At Most N Given Digit Set

// 0902. Numbers At Most N Given Digit Set
class Solution {
    public int atMostNGivenDigitSet(String[] digits, int n) {
        int quantity = digits.length;
        char[] digitsArray = new char[quantity];
        for (int i = 0; i < quantity; ++i) {
            digitsArray[i] = digits[i].charAt(0);
        }
        char[] nArray = String.valueOf(n).toCharArray();
        int len = nArray.length;
        int[] memory = new int[len];
        memory[0] = 1;
        for (int i = 1; i < len; ++i) {
            memory[i] = memory[i - 1] * quantity;
        }
        int ans = 0;
        for (int i = 1; i < len; ++i) {
            ans += memory[i];
        }
        ans += dfs(0, digitsArray, quantity, nArray, len, memory);
        return ans;
    }

    private int dfs(int currentIndex, char[] digitsArray, int quantity, char[] nArray, int len, int[] memory) {
        if (currentIndex == len) return 1;
        int ret = 0;
        for (int i = 0; i < quantity; ++i) {
            if (digitsArray[i] < nArray[currentIndex]) {
                ret += memory[len - 1 - currentIndex];
            } else if (digitsArray[i] == nArray[currentIndex]) {
                ret += dfs(currentIndex + 1, digitsArray, quantity, nArray, len, memory);
            }
        }
        return ret;
    }
}
学习笔记:
今天这是一道困难题,所以做到了大半夜1:40才做完。
可以用数位DP,数学算法,记忆化搜索。
我很久没写记忆化搜索了,就写了记忆化搜索的。
主要是分类讨论,首先是位数少于n的,那就直接排列就行了。
1位的全排列、2位的全排列、3位的全排列,然后加起来。

位数相同的话,就要开始深搜了。
先从第0位开始看,我们的材料比n的第0位小,那就说明后面可以随便排列。
ret += memory[len - 1 - currentIndex];
我们的材料比n的第0位大,
那就说明,后面怎么排列都没用了。
我们的材料比n的第0位相等,那就得看后面的排列了,变成了原问题的子问题。
ret += dfs(currentIndex + 1, digitsArray, quantity, nArray, len, memory);

最后要注意一点,如果最后的一位相等,那再进入深搜的时候,就会触发base case,我一开始的base case写错了,返回了0,应该返回的是1。
if (currentIndex == len) return 1;
为什么是1呢,我们要想一下,最后一位相等了,那说明这个数字是等于n的,题目要求就是小于等于n,之前过程中我们也没加过这个1,所以这里我们需要加1。
发表在 每日LeetCode | 留下评论

Daily LeetCode – day0080 0904. Fruit Into Baskets

import java.util.HashMap;

// 0904. Fruit Into Baskets
class Solution {
    public int totalFruit(int[] fruits) {
        int len = fruits.length;
        HashMap<Integer, Integer> slidingWindow = new HashMap<>();
        int left = 0;
        int right = 0;
        int ans = 0;
        while (right < len) {
            slidingWindow.put(fruits[right], slidingWindow.getOrDefault(fruits[right], 0) + 1);
            if (slidingWindow.size() < 3) {
                ans = Math.max(ans, right - left + 1);
            } else {
                while (slidingWindow.size() > 2) {
                    if (slidingWindow.get(fruits[left]) == 1) {
                        slidingWindow.remove(fruits[left]);
                    } else {
                        slidingWindow.put(fruits[left], slidingWindow.get(fruits[left]) - 1);
                    }
                    ++left;
                }
            }
            ++right;
        }
        return ans;
    }
}
学习笔记:
这道题水果成篮去年7月17日做过,是一道滑动窗口的经典题目。
去年做的时候比较猛,直接不用hashmap,而使用四个变量维护住了窗口。
今天写的时候已经是下班到家的10点了,所以就偷懒用了hashmap。
发表在 每日LeetCode | 留下评论

Daily LeetCode – day0079 0886. Possible Bipartition

import java.util.ArrayList;
import java.util.LinkedList;

// 0886. Possible Bipartition
class Solution {
    public boolean possibleBipartition(int n, int[][] dislikes) {
        UnionFind unionFind = new UnionFind(n + 1);
        ArrayList<LinkedList<Integer>> enemiesByNumber = new ArrayList<>();
        for (int i = 0; i <= n; ++i) enemiesByNumber.add(new LinkedList<>());
        for (int[] dislike : dislikes) {
            enemiesByNumber.get(dislike[0]).add(dislike[1]);
            enemiesByNumber.get(dislike[1]).add(dislike[0]);
        }
        for (int i = 1; i <= n; ++i) {
            LinkedList<Integer> enemies = enemiesByNumber.get(i);
            if (!enemies.isEmpty()) {
                Integer enemiesFirst = enemies.getFirst();
                for (Integer enemy : enemies) {
                    if (unionFind.find(i) == unionFind.find(enemy)) {
                        return false;
                    }
                    unionFind.union(enemiesFirst, enemy);
                }
            }
        }
        return true;
    }
}

class UnionFind {
    int[] parent;

    public UnionFind(int n) {
        parent = new int[n];
        for (int i = 0; i < n; ++i) {
            parent[i] = i;
        }
    }

    public int find(int son) {
        if (parent[son] == son) {
            return son;
        }
        parent[son] = find(parent[son]);
        return parent[son];
    }

    public void union(int a, int b) {
        int parentOfA = find(a);
        int parentOfB = find(b);
        if (parentOfA != parentOfB) {
            parent[parentOfA] = parentOfB;
        }
    }
}
学习笔记:
今天是一道染色相关的问题,深搜光搜都可以做的。
但是我觉得没大难度的话还是复习并查集吧。
但是一下子也没想通,看了一些题解后才想通。
一开始每个人老大是自己,然后每次遍历一个人的敌人,就要把他的敌人都丢去第一个敌人的帮派。一旦发现有敌人出现在自己的帮派说明发生了矛盾,就直接返回false。
发表在 每日LeetCode | 留下评论

Daily LeetCode – day0078 1441. Build an Array With Stack Operations

import java.util.LinkedList;
import java.util.List;

// 1441. Build an Array With Stack Operations
class Solution {
    public List<String> buildArray(int[] target, int n) {
        int len = target.length;
        int maximum = target[len - 1];
        LinkedList<String> ans = new LinkedList<>();
        for (int i = 0, f = 1; f <= maximum; ++f) {
            if (target[i] == f) {
                ans.add("Push");
                ++i;
            } else {
                ans.add("Push");
                ans.add("Pop");
            }
        }
        return ans;
    }
}
学习笔记:
今天是一道标着中等的简单题。
其实就是把数字都遍历一遍,对上了就加个Push,对不上就Push Pop都加一个。
那个n的变量完全没有用,属实迷惑。
发表在 每日LeetCode | 留下评论

Daily LeetCode – day0077 0940. Distinct Subsequences II

// 0940. Distinct Subsequences II
class Solution {
    public int distinctSubseqII(String s) {
        char[] chars = s.toCharArray();
        int len = chars.length;
        final int MOD = 1000000007;
        int[] lastTimeIncrementByLetter = new int[123];
        int[] subsequences = new int[len];
        subsequences[0] = 2;
        lastTimeIncrementByLetter[chars[0]] = 1;
        for (int i = 1; i < len; ++i) {
            int previous = subsequences[i - 1];
            subsequences[i] = (previous * 2 % MOD - lastTimeIncrementByLetter[chars[i]] % MOD + MOD) % MOD;
            lastTimeIncrementByLetter[chars[i]] = previous;
        }
        return subsequences[len - 1] - 1;
    }
}
学习笔记:
今天是一道困难题唯一子序列。
看了题解后发现这道题的递推原来是用哈希表记录上一次出现这个字母的添加的数量,然后这次减去。这其实就是可能重复的情况了。
这道题还有一点恶心的就是数量可能很大,需要模,模结果也不对,模前面也不对。是需要前面模,后面模,减完整个再模。属实烦啊。
发表在 每日LeetCode | 留下评论

Daily LeetCode – day0076 0769. Max Chunks To Make Sorted

// 0769. Max Chunks To Make Sorted
class Solution {
    public int maxChunksToSorted(int[] arr) {
        int len = arr.length;
        int leftMax = -1;
        int ans = 0;
        for (int i = 0; i < len; ++i) {
            if (arr[i] > leftMax) leftMax = arr[i];
            if (leftMax == i) ++ans;
        }
        return ans;
    }
}
学习笔记:
今天是一道栈的题目,这道题目之前在蓝桥杯的时候给学生讲解过。
但是其实上不需要排序那么复杂的。
只需要看左边过去的数的最大值有没有和索引对上,如果刚好对上了,说明左边肯定刚刚好,可以排序好变成一节。然后再往右边数。
发表在 每日LeetCode | 留下评论