1461. Check If a String Contains All Binary Codes

1461. Check If a String Contains All Binary Codes

Question

Given a binary string s and an integer k, return true if every binary code of length k is a substring of s. Otherwise, return false.

Solution

位运算+哈希表,用整数num记录二进制编码并加入哈希表中。
首先将字符串内的前k位二进制码转换为整数。并将其数值添加到哈希表内。

滑动窗口遍历,维持num为k长度内二进制码所对应的整数,并在遍历时将num加入哈希集合。
如果哈希集合的size达到k长度对应的可组合数,则返回true,否则返回false。

位运算与掩码

为了维护整数num,首先将其二进制编码向左移动一位。
然后与mask进行按位与运算,屏蔽掉多余的位数。

mask的值为k长度对应的可组合数-1。

Code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Solution {
public boolean hasAllCodes(String s, int k) {
if(s.length() < k) return false; //字符串长度不足则返回false
char[] bin = s.toCharArray();
int n = s.length(), m = (int) Math.pow(2, k);
HashSet<Integer> set = new HashSet<>(); //哈希表记录访问过的数字

int num = Integer.parseInt(s.substring(0,k), 2); //字符串以二进制转换为整数
int mask = m-1; //掩码等于寻找长度-1
set.add(num);

for(int i = k; i < s.length(); i++){ //位运算,维护窗口内整数的值
num <<= 1; //位运算左移一位
num &= mask; //出界的位置被掩码遮盖
if(bin[i] == '1') num++;
set.add(num);
if(set.size() == m) return true; //如果哈希表长度等于2^k,则已经遍历了所有组合,返回true
}
return false;
}
}
29. Divide Two Integers

29. Divide Two Integers

Question

Given two integers dividend and divisor, divide two integers without using multiplication, division, and mod operator.

The integer division should truncate toward zero, which means losing its fractional part. For example, 8.345 would be truncated to 8, and -2.7335 would be truncated to -2.

Return *the quotient after dividing dividend by *divisor.

**Note: **Assume we are dealing with an environment that could only store integers within the 32-bit signed integer range: [−2<sup>31</sup>, 2<sup>31</sup><span> </span>− 1]. For this problem, if the quotient is strictly greater than 2<sup>31</sup><span> </span>- 1, then return 2<sup>31</sup><span> </span>- 1, and if the quotient is strictly less than -2<sup>31</sup>, then return -2<sup>31</sup>.

Solution

解题思路类似于快速幂

使用快速乘法来快速的获得商。

计算过程相当于:
60/8 = (60-32)/8 + 4 = (60-32-16)/8 + 2 + 4 = 1 + 2 + 4 = 7

需要注意的是由于只使用了整数(int)而不是长整数(long)储存数据,因此计算时需要处理各种溢出问题。

整数溢出

由于采用32位整数记录数字,负数要比正数的值范围大1。
因此当divisor为负数时,如果负数为整数最小值,则需要返回对应的整数最大值。

同时,为了在计算时防止整数溢出,因此将被除数与除数统一转为负数计算。(负数的数值比整数范围大)
当向下递归时,要保持dividend和divisor的正负性不变。

快速乘

只要被除数大于除数,则商至少为1。
循环,当被除数大于两倍的除数时,则商的结果可以直接翻倍。

否则将被除数减去当前的除数,然后向下递归新的被除数和除数。
最后返回快速乘中计算出的商加上向下递归返回的结果。

Code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Solution {
public int divide(int dividend, int divisor) {
if(dividend == 0) return 0;
if(divisor == 1) return dividend;
if(divisor == -1) return dividend == Integer.MIN_VALUE ? Integer.MAX_VALUE : -dividend; //当dividend为最小整数时,其负数溢出,此时返回Integer.MAX_VALUE
int a = dividend, b = divisor;
int sign = a > 0 && b > 0 || a < 0 && b < 0 ? 1 : -1; //记录除数与被除数是否同号
if(dividend > 0) a = -a; //将除数与被除数转换为负数,因为负数能记录的数值比正数大1,防止溢出
if(divisor > 0) b = -b;
if(a > b) return 0; //被除数小于除数时返回0
int res = 1;
while(a <= b+b && b+b < 0){ //算法核心,快速乘法
b += b; //除数每次翻倍,直到大于被除数
res += res; //商的结果翻倍
}
res = sign == 1 ? res : -res; //根据是否同号记录商

divisor = dividend > 0 ? -divisor : divisor; //当原有被除数是正数时,要将除数取反
return res + divide(a-b, divisor);
}
}
318. Maximum Product of Word Lengths

318. Maximum Product of Word Lengths

Question

Given a string array words, return the maximum value of length(word[i]) * length(word[j]) where the two words do not share common letters. If no such two words exist, return 0.

Solution

正常的思路可能是用哈希表保存字符串中出现过的字符。此时表的大小是26也是一个常数。

我们可以采用位运算进行状态压缩,通过一个整数的二进制位保存二十六个字符的存在状态。

状态压缩

我们采用一个整数数组bin[]储存状态压缩后的整数。

遍历每个字符串,在字符串中遍历每一个字符。
初始化整数bit为0。在遍历字符时计算当前字符与字符’a’距离的差n,然后将1左移n位,填入(或运算)整数bit中。

比较

遍历words[]中的两个字符串,当bin[i]与bin[j]进行与运算结果为0时,则两个字符串没有共同字符。
此时更新二者长度乘积的最大值max。

最后返回max即可。

时间复杂度:O(L + n2)。L为所有字符的总长度。

Code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Solution {
public int maxProduct(String[] words) {

int max = 0;
int bin[] = new int[words.length];

for(int i = 0; i < words.length; i++){
int bit = 0;
for(int j = 0; j < words[i].length(); j++){
bit |= (1 << (words[i].charAt(j) - 'a'));
}
bin[i] = bit;
}

for(int i = 0; i < words.length; i++){
for(int j = i+1; j < words.length; j++){
if((bin[i] & bin[j]) == 0) max = Math.max(max, words[i].length() * words[j].length());
}
}
return max;
}
}
268. Missing Number

268. Missing Number

Question

Given an array nums containing n distinct numbers in the range [0, n], return the only number in the range that is missing from the array.

Solution 1

采用数学方法,从0到n的和减去从nums中的每个元素的和,得到的即是缺失的数字。
循环所有下标,每次在结果上加上下标的值,并减去下标对应的元素。最后需要再加上nums内元素数量+1的值。

Code

1
2
3
4
5
6
7
8
9
10
11
class Solution {
public int missingNumber(int[] nums) {
int sum = nums.length;
for(int i = 0; i < nums.length; i++){
sum -= nums[i];
sum += i;
}

return sum;
}
}

Solution 2

遍历,采用一个数组found[]记录是否访问过。
再次遍历,如果存在未访问过的位置,则返回下标。

Code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Solution {
public int missingNumber(int[] nums) {
int res = 0;
int[] found = new int[nums.length+1];
for(int i = 0; i < nums.length; i++){
found[nums[i]] = 1;
}

for(int i = 0; i <= nums.length; i++){
if(found[i] == 0) res = i;
}
return res;
}
}
1342. Number of Steps to Reduce a Number to Zero

1342. Number of Steps to Reduce a Number to Zero

Question

Given an integer num, return the number of steps to reduce it to zero.

In one step, if the current number is even, you have to divide it by 2, otherwise, you have to subtract 1 from it.

Solution

循环,当num不等于零时,如果为奇数则减一,如果为偶数则除以2。
每次循环记录次数。

Code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Solution {
public int numberOfSteps(int num) {
int count =0;
while(num != 0){
count++;
if((num & 1) == 0){
num/=2;
}
else{
num--;
}
}
return count;
}
}
354. Russian Doll Envelopes

354. Russian Doll Envelopes

Question

You are given a 2D array of integers envelopes where envelopes[i] = [w<sub>i</sub>, h<sub>i</sub>] represents the width and the height of an envelope.

One envelope can fit into another if and only if both the width and height of one envelope are greater than the other envelope’s width and height.

Return the maximum number of envelopes you can Russian doll (i.e., put one inside the other).

Note: You cannot rotate an envelope.

Solution 1

最长递增子数列

这道题是LIS问题(Longest Increasing Subsequence) 的升级版。
后一个数组中的两个元素应该同时严格大于前一个数组中的两个元素。
因此我们需要维护一个宽度和高度同时严格单调递增的数列。

排序

我们可以对数组以宽度进行比较排序。当二者的宽度相等时,以高度的倒序排序
(采用倒序排序是为了下面处理时排除不严格大于上一个数组的情况。)

递增数列

我们可以维护一个递增动态数组arr,记录高度组成的单调递增数列。(LIS)
首先将数组中的第一个元素的高度加入动态数组。
然后遍历数组envelopes[]:

  • 如果当前数组的高度height大于单调递增数列的最大值:
    • 则当前的数组的宽度高度均严格大于数组中最后一个高度对应的数组。
    • 数组是优先以宽度的顺序排列,相等时以高度的倒序排列。由于相同宽度时高度大的在前,因此当当前高度大于之前的最大高度,则宽度也一定大于之前的最大宽度。
  • 如果小于或者等于单调递增数列的最大值:
    • 单调数列中高度的增长越平缓,我们在后面找到更多严格大于height的可能性就越大,因此可以用贪心思想,更新单调数列中的第一个大于当前高度的值。
    • 由于单调递增数列是有序的,因此可以采用二分搜索来寻找第一个大于当前高度的值的下标,并将其更新为当前的height。

排序的时间复杂度为O(nlogn),遍历数组的时间复杂度为O(n),二分搜索的时间复杂度为O(logn)。
总的时间复杂度为O(nlogn)。

Code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Solution {
public int maxEnvelopes(int[][] envelopes) {
Arrays.sort(envelopes, (a,b) -> a[0] != b[0] ? a[0] - b[0] : b[1] - a[1]);

List<Integer> arr = new ArrayList<>();
arr.add(envelopes[0][1]);
for(int i = 1; i < envelopes.length; i++){
int height = envelopes[i][1];
if(height > arr.get(arr.size()-1)){
arr.add(height);
}
else{
int index = Collections.binarySearch(arr, height);
if(index < 0) index = -index-1;
arr.set(index, height);
}
}
return arr.size();
}
}

Solution 2

注意这个方法在LeetCode中会超时,仅仅是记录一下思路,也可以采用动态规划的方法。
记忆化搜索剪枝。
记忆化搜索每个envelope。

memorazation()方法,返回当前的envelope中可以包含的最大数量。
当数组memo[i]不等于0时,返回memo[i]。
否则初始化最大值max为1。
遍历数组中的所有元素,如果宽度于长度都大于envelopes[i],则向下递归搜索该元素。
比较并更新max和递归结果的返回值+1。
最后将当前位置memo[i]设置为包含的最大数量max。

遍历所有元素时间复杂度O(n)。
记忆化搜索元素的子项时间复杂度O(n)。
时间复杂度为O(n2)。

Code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
class Solution {
int[][] dolls;
int[] memo;

public int maxEnvelopes(int[][] envelopes) {
dolls = envelopes;
memo = new int[dolls.length];
int res = 0;
for(int i = 0; i < dolls.length; i++){
if(memo[i] != 0) continue;
memorazation(i);
res = Math.max(res, memo[i]);
}
return res;
}

private int memorazation(int i){
if(memo[i] != 0){
return memo[i];
}
int max = 1;
for(int k = 0; k < dolls.length; k++){
if(dolls[k][0] < dolls[i][0] && dolls[k][1] < dolls[i][1]){
max = Math.max(max, memorazation(k) + 1);
}
}
return memo[i] = max;
}
}
32. Longest Valid Parentheses

32. Longest Valid Parentheses

Question

Given a string containing just the characters '(' and ')', find the length of the longest valid (well-formed) parentheses substring.

Solution

如果有右括号先出现于左括号,则其永远无法配对。
而如果是左括号先出现,则其有可能可以和之后的右括号进行配对。

因此我们可以将左括号的位置入栈,等待配对。
并记录上一个不能配对的右括号的位置。以此来确定当前位置的有效括号长度。

首先将上一个不能组队的右括号lastRightParenthese初始化为-1。
初始化一个最大有效长度best。

然后遍历字符串:

  • 当遇到左括号时:
    • 将当前下标i压入栈。(记录等待配对的左括号)
  • 遇到右括号时:
    • 如果栈为空,则更新上一个不能组队的右括号的位置为当前下标i。(此时出现的右括号一定无法配对)
    • 如果栈不为空,则挤出上一个左括号的下标,与右括号配对。
      • 挤出上一个左括号后,如果栈为空。则将当前的有效括号总长度为i - lastRightParenthese。(当前的右括号和上一个不能配对的右括号的差)
      • 否则当前的有效括号总长度为i - stack.peek()。(当前的右括号和上一个不配对的左括号的差。)

Code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class Solution {
public int longestValidParentheses(String s) {
Deque<Integer> stack = new LinkedList<>();
int best = 0;
int lastRightParenthese = -1; //上一个不能配对的右括号位置,初始化为-1
for(int i = 0; i < s.length(); i++){
if(s.charAt(i) == '('){ //如果当前位置是左括号,则将下标入栈
stack.push(i);
}
else{ //如果当前位置是右括号...
if(stack.isEmpty()){ //如果没有可以配对的左括号,则更新不能配对的右括号的位置
lastRightParenthese = i;
}
else{
stack.pop(); //如果有可以配对的右括号,则从栈中挤出
if(stack.isEmpty()){ //挤出后如果没有剩余的左括号了,则当前有效括号长度为当前位置减去上一个无法配对的右括号的位置
best = Math.max(best, i - lastRightParenthese);
}
else{ //如果挤出后栈内仍有剩余的左括号存在,则当前有效括号长度为当前位置减去栈顶的左括号的位置
best = Math.max(best, i - stack.peek());
}
}
}
}
return best;
}
}
474. Ones and Zeroes

474. Ones and Zeroes

Question

You are given an array of binary strings strs and two integers m and n.

Return the size of the largest subset of strs such that there are at most m 0‘s and n 1‘s in the subset.

A set x is a subset of a set y if all elements of x are also elements of y.

Solution 1

0-1背包问题的升级版。经典的背包问题只有一种容量,而这个问题实际等于有两种容量。

经典背包问题需要用二维数组动态规划,分别为物品和容量。
而这道题需要用三维数组动态规划,分别为字符串,0的容量和1的容量。

我们采用一个三维数组dp[i][j][k]保存可取最多物品的数量
对于每个物品i,我们都可以取(数量+1)或不取(保持上一个物品的状态)。
j和k相当于分别记录了取“0”的总数和“1”的总数

遍历三个维度,并计算字符串中0和1的个数。

  • 如果当总容量大于当前物品中zeros和ones的数量,则可以取当前的物品

    • 如果取当前的物品,则dp[]数组的总数等于上一个物品的状态加一,即dp[i-1][j-zeros][k-ones]。
    • 也可以不取当前的物品,则dp[]数组的总数直接保持上一个物品的状态
  • 如果当前背包总容量小于zeros和ones的数量,则不能取当前物品。

    • 数组dp[]保持上一个物品的状态

时间复杂度:O(lmn + L),L是数组中所有字符串长度之和。
空间复杂度:O(lmn)。

Code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Solution {
public int findMaxForm(String[] strs, int m, int n) {
int[][][] dp = new int[strs.length+1][m+1][n+1];

for(int i = 1; i <= strs.length; i++){
int zeros = 0, ones = 0;
for(char c : strs[i-1].toCharArray()){
if(c == '0') zeros++;
else ones++;
}

for(int j = 0; j <= m; j++){
for(int k = 0; k <= n; k++){
if(j - zeros >= 0 && k - ones >=0) dp[i][j][k] = Math.max(dp[i-1][j][k], dp[i-1][j-zeros][k-ones] + 1);
else dp[i][j][k] = dp[i-1][j][k];
}
}
}
return dp[strs.length][m][n];
}
}

Solution 2

由于dp[i][][]的上一个状态只涉及到dp[i-1][][],因此我们可以采用滚动数组,去掉数组的一个维度,压缩空间。
此时需要从m到zeros,从n到ones倒序遍历,保证dp[i][][]转移过来的是dp[i-1][][]的元素。

此时的空间复杂度降低为O(mn)。

Code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Solution {
public int findMaxForm(String[] strs, int m, int n) {
int[][] dp = new int[m+1][n+1];

for(String s : strs){ //记录每个位置对应的0和1数量
int zeros = 0, ones = 0;
for(char c : s.toCharArray()){
if(c == '0') zeros++;
else ones++;
}

for(int i = m; i >= zeros; i--){
for(int j = n; j >= ones; j--){
dp[i][j] = Math.max(dp[i][j], dp[i-zeros][j-ones] + 1);
}
}
}
return dp[m][n];
}
}

Solution 3

递归+记忆化搜索,保存并返回每个位置的最大长度。
如果位置越界则返回0。
如果memo[i][zero][one]不为0,则返回memo[i][zero][one]。

如果当前字符串的“0”和“1”的个数大于剩余的“0”和“1”的个数,则可以取当前字符串,递归下一个位置并加一。
也可以不取当前的字符串,递归下一个位置。当前位置memo[i][zero][one]等于两者中的最大值。

否则无法取当前字符串,直接递归下一个位置。当前位置等于0。

Code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
class Solution {
String[] s;
int[] zeros, ones;
int[][][] memo;
public int findMaxForm(String[] strs, int m, int n) {
s = strs;
zeros = new int[strs.length];
ones = new int[strs.length];
memo = new int[strs.length][m+1][n+1];

for(int i = 0; i < strs.length; i++){ //记录每个位置对应的0和1数量
for(int j = 0; j < strs[i].length(); j++){
if(s[i].charAt(j) == '0') zeros[i]++;
else ones[i]++;
}
}
return dfs(0, m, n);
}

private int dfs(int i, int zero, int one){
if(i >= s.length){
return 0;
}
if(memo[i][zero][one] != 0) return memo[i][zero][one];

int maxPossibleSubset = 0;

if(zero-zeros[i] >= 0 && one-ones[i] >= 0){
maxPossibleSubset = 1 + dfs(i+1, zero-zeros[i], one-ones[i]);
}
memo[i][zero][one] = Math.max(maxPossibleSubset, dfs(i+1, zero, one));
return memo[i][zero][one];

}
}

2281. Sum of Total Strength of Wizards

Question

As the ruler of a kingdom, you have an army of wizards at your command.

You are given a 0-indexed integer array strength, where strength[i] denotes the strength of the i<sup>th</sup> wizard. For a contiguous group of wizards (i.e. the wizards’ strengths form a subarray of strength), the total strength is defined as the product of the following two values:

  • The strength of the weakest wizard in the group.
  • The total of all the individual strengths of the wizards in the group.

Return the sum of the total strengths of all contiguous groups of wizards. Since the answer may be very large, return it modulo 10<sup>9</sup><span> </span>+ 7.

A subarray is a contiguous non-empty sequence of elements within an array.

Solution

参考连接

根据题意,我们需要的结果是所有子数列乘以该子数列中最小值之和。

数组strength[]中的每一个元素,都充当一定范围内的子数列的最小值。因此我们需要**确定每个元素充当最小值的范围(left, right)**。然后计算这个元素可以组成的子数列的和与这个元素的乘积。

我们的问题相当于确定下一个更小元素,而单调栈专门处理这一类问题——“下一个更大/更小元素”
496. Next Greater Element I

通过单调栈,我们可以确定最小值为strength[i]的子数列的范围(left, right),并将其保存在两个数组left[]与right[]中。

最后通过前缀和的计算,来确定(left, right)范围内的子数列之和

单调栈

新建两个数组left[], right[]保存以strength[i]为最小值的范围。

维护两个单调栈,保证下列关系:
strength[left] < strength[i]
strength[right] <= strength[i]

其中,left取小于号,而right可以等于是为了取值不重复。

注意,单调栈保存的是下标,这样使用起来更加灵活。

前缀和

为了得到范围(left, right)内的所有子数列之和。我们可以观察left, i, right之间的关系。

left-1, left, left + 1, left + 2, … i-1, i, i+1, … right-1, right, right+1

  • 开始于 left+1的子数列:
    sum(left+1, … i) = prefix[i + 1] - prefix[left + 1]
    sum(left+1, … i+1) = prefix[i + 2] - prefix[left + 1]

    sum(left+1, … right-1) = prefix[right] - prefix[left + 1]
  • 开始于left+2的子数列:
    sum(left+2, … i) = prefix[i + 1] - prefix[left + 2]
    sum(left+2, … i+1) = prefix[i + 2] - prefix[left + 2]

    sum(left+2, … right-1) = prefix[right] - prefix[left + 2]
  • 开始于i的子数列:
    sum(i, … i) = prefix[i + 1] - prefix[i]
    sum(i, … i+1) = prefix[i + 2] - prefix[i]

    sum(i, … right-1) = prefix[right] - prefix[i]

合并这些式子,我们可以得到:

  • 正数 部分:
    (prefix[i + 1] + prefix[i + 2] + ... + prefix[right]) * (i - left)
  • 负数 部分:
    (prefix[left + 1] + prefix[left + 2] + ... + prefix[i]) * (right - i)

最后我们可以提前计算前缀和的前缀和来优化计算步骤,注意计算时需要将各个部分模除以保证不超过范围。
正数部分模除后需要加上MOD以保证其减去负数部分的结果大于0。

Code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
class Solution {
public int totalStrength(int[] strength) {
long MOD = 1_000_000_007;
int n = strength.length;

Deque<Integer> mono = new ArrayDeque<>();
int[] left = new int[n], right = new int[n];

for(int i = 0; i < strength.length; i++){ //单调栈,记录比当前元素strength[i]小的第一个左侧位置strength[left]
while(!mono.isEmpty() && strength[mono.peek()] >= strength[i]){
mono.pop();
}
left[i] = mono.isEmpty() ? -1 : mono.peek();
mono.push(i);
}
mono.clear();
for(int i = n-1; i >=0; i--){ //单调栈,记录比当前元素strength[i]小的第一个右侧位置strength[right]
while(!mono.isEmpty() && strength[mono.peek()] > strength[i]){
mono.pop();
}
right[i] = mono.isEmpty() ? n : mono.peek();
mono.push(i);
}

long[] prefix = new long[n+1], prefix_sum = new long[n+2];

for(int i = 1; i <= strength.length; i++){ //计算前缀和
prefix[i] = (prefix[i-1] + strength[i-1]) % MOD;
}

for(int i = 2; i <= strength.length+1; i++){ //计算前缀和的前缀和
prefix_sum[i] = (prefix_sum[i-1] + prefix[i-1]) % MOD;
}

long res = 0;

for(int i = 0; i < n; i++){
res += ((prefix_sum[right[i] + 1] - prefix_sum[i + 1]) * (i - left[i]) % MOD + MOD - //这里加MOD是为了保证前项大于后项,防止出现负数
(prefix_sum[i + 1] - prefix_sum[left[i] + 1]) * (right[i] - i) % MOD ) * strength[i];
res %= MOD;
}

return (int) res;
}
}

2280. Minimum Lines to Represent a Line Chart

Question

You are given a 2D integer array stockPrices where stockPrices[i] = [day<sub>i</sub>, price<sub>i</sub>] indicates the price of the stock on day day<sub>i</sub> is price<sub>i</sub>. A line chart is created from the array by plotting the points on an XY plane with the X-axis representing the day and the Y-axis representing the price and connecting adjacent points. One such example is shown below:

Solution

如果只有一个点,无法组成线段则返回0。否则至少可以组成1条线段。

首先将所有点根据x的位置排序,然后一次比较连续的各个向量的方向。
为了防止除法精度问题,计算两个向量(三个点组成两个向量)的叉乘,如果叉乘为0,则说明两个向量是平行的,此时不需要增加计数。
如果叉乘结果不等于0,则将计数+1。

最后返回计数的结果。

Code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Solution {
public int minimumLines(int[][] stockPrices) {
if(stockPrices.length == 1) return 0;

Arrays.sort(stockPrices, (a,b) -> a[0] - b[0]);
int count = 1, lastX = 0, lastY = 0;

for(int i = 1; i < stockPrices.length; i++){
int x = stockPrices[i][0] - stockPrices[i-1][0];
int y = stockPrices[i][1] - stockPrices[i-1][1];

int k = lastX * y - lastY * x;
if(k != 0) count++;
lastX = x;
lastY = y;
}
return count;
}
}

2279. Maximum Bags With Full Capacity of Rocks

Question

You have n bags numbered from 0 to n - 1. You are given two 0-indexed integer arrays capacity and rocks. The i<sup>th</sup> bag can hold a maximum of capacity[i] rocks and currently contains rocks[i] rocks. You are also given an integer additionalRocks, the number of additional rocks you can place in any of the bags.

Return* the maximum number of bags that could have full capacity after placing the additional rocks in some bags.*

Solution

计算每个背包的剩余空间,然后进行排序。
按从小到大的顺序依次减少石头的总数。
设置i作为选取石头的计数。每次循环将i增加,直到剩余的石头数量小于0。
如果最后剩下的石头仍然大于等于0,则返回计数。
否则返回计数-1。

Code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Solution {
public int maximumBags(int[] capacity, int[] rocks, int additionalRocks) {
int[] diff = new int[capacity.length];
for(int i = 0 ; i < capacity.length; i++){
diff[i] = capacity[i] - rocks[i];
}

Arrays.sort(diff);
int i = 0;
while(additionalRocks > 0 && i < diff.length){
additionalRocks -= diff[i];
i++;
}
return additionalRocks >= 0 ? i : i - 1;
}
}

2278. Percentage of Letter in String

Question

Given a string s and a character letter, return* the percentage of characters in s that equal letter rounded down to the nearest whole percent.*

Solution

一次遍历,计算字符出现的次数。
返回字符出现的次数除以字符长度乘以100。

Code

1
2
3
4
5
6
7
8
9
10
11
class Solution {
public int percentageLetter(String s, char letter) {
int count = 0;
for(int i = 0; i < s.length(); i++){
if(s.charAt(i) == letter){
count++;
}
}
return count * 100 / s.length();
}
}
647. Palindromic Substrings

647. Palindromic Substrings

Question

Given a string s, return the number of palindromic substrings in it.

A string is a palindrome when it reads the same backward as forward.

A substring is a contiguous sequence of characters within the string.

Solution

Manacher算法,与5. Longest Palindromic Substring的实现方法相同。

首先处理字符串。
然后采用中心拓展计算各个位置的拓展长度,并维护c与r。
每次遍历时计算结果,将当前可扩展长度加一然后除以二后加入结果。

Code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
class Solution {
int n;
int[] p;
String newString;
public int countSubstrings(String s) {
n = s.length();
StringBuffer sb = new StringBuffer();
if(n == 0) { //对字符串进行处理
sb.append("^$");
}
else{
sb.append("^");
for(int i = 0; i < s.length(); i++){
sb.append('#');
sb.append(s.charAt(i));
}
sb.append("#$");
}
p = new int[sb.length()];
newString = sb.toString();

return manacher();
}

private int manacher(){
int res = 0, c = 0, r = 0;
for(int i = 1; i < newString.length()-1; i++){
int m = 2 * c - i;
if(i < r) p[i] = Math.min(r-i, p[m]);

while(newString.charAt(i+p[i]+1) == newString.charAt(i-p[i]-1)){ //中心拓展
p[i]++;
}

if(i+p[i] > r){ //更新c与r
c = i;
r = i + p[i];
}
res += (p[i]+1)/2; //向上取整
}
return res;
}
}

Solution 2

中心拓展,辅助方法count()用来计算每个字符可以组成的最大回文数。

注意每次需要计算奇数长度回文和偶数长度回文两种情况。
遍历所有字符并加和,返回总数。

Code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
class Solution {
String word;
public int countSubstrings(String s) {
word = s;
int ret = 0;
for(int i = 0; i < s.length(); i++){
ret+=count(i);
}
return ret;
}

private int count(int i){
int count = 1, left = i-1, right = i+1;

while(left >= 0 && right < word.length() && word.charAt(left) == word.charAt(right)){
count++;
left--;
right++;
}

left = i;
right = i+1;

while(left >= 0 && right < word.length() && word.charAt(left) == word.charAt(right)){
count++;
left--;
right++;
}
return count;
}
}
63. Unique Paths II

63. Unique Paths II

Question

You are given an m x n integer array grid. There is a robot initially located at the top-left corner (i.e., grid[0][0]). The robot tries to move to the bottom-right corner (i.e., grid[m-1][n-1]). The robot can only move either down or right at any point in time.

An obstacle and space are marked as 1 or 0 respectively in grid. A path that the robot takes cannot include any square that is an obstacle.

Return the number of possible unique paths that the robot can take to reach the bottom-right corner.

The testcases are generated so that the answer will be less than or equal to 2 * 10<sup>9</sup>.

Solution

机器人只朝向一个方向移动,不存在折返。
因此可以采用动态规划,直接记录到某个点的总路线数。
当遇到障碍时(即当前访问的格子为1)则不更新dp[]数组。

Code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Solution {
public int uniquePathsWithObstacles(int[][] obstacleGrid) {

int m = obstacleGrid.length;
int n = obstacleGrid[0].length;

int[][] dp = new int[m+1][n+1];
dp[0][1] = 1;

for(int x = 1; x < m+1; x++){
for(int y = 1; y < n+1; y++){
if(obstacleGrid[x-1][y-1] == 0){
dp[x][y] = dp[x][y-1] + dp[x-1][y];
}
}
}
return dp[m][n];
}
}

329. Longest Increasing Path in a Matrix

Question

Given an m x n integers matrix, return *the length of the longest increasing path in *matrix.

From each cell, you can either move in four directions: left, right, up, or down. You may not move diagonally or move outside the boundary (i.e., wrap-around is not allowed).

Solution

第一次就通过DFS和DP的知识自己摸索出了记忆化搜索,非常开心!: )

记忆化搜索,采用DFS搜索,并动态规划的思想保存搜索结果。

递归计算从x, y点出发的最大长度,并在递归中记录子位置的最大长度。
通过子位置的最大长度对递归树进行剪枝。

记忆化搜索

初始化一个memo[][]数组,记录已经搜索过的x, y位置的最大长度。
对所有下一个位置进行DFS搜索,如果越界或不满足递增规律,则返回最大长度1。
(这里应该返回0,但是为了下一步的记忆化搜索判断的遍历因此返回1。如果返回0的话则后面返回最大值不需要-1,但此时由于会重复搜索因此速度会降低。)

如果x, y的位置已经搜索过了(即memo[x][y] != 0),则直接返回memo[x][y]的值+1。

搜索完所有位置的最大长度,将其中的最大值保存在memo[][]中。
最后返回最大值+1。

将每个位置遍历,返回memo[][]中的最大值。

Code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
class Solution {
int[][] mat, memo, moves = new int[][]{{1, 0}, {-1, 0}, {0, 1}, {0, -1}};
int m, n, max;

public int longestIncreasingPath(int[][] matrix) {
mat = matrix;
m = matrix.length;
n = matrix[0].length;
memo = new int[m][n];

max = 0;
for(int x = 0; x < m; x++){
for(int y = 0; y < n; y++){
dfs(x, y, -1);
max = Math.max(max, memo[x][y]);
}
}
return max;
}

private int dfs(int x, int y, int parent){
if(x < 0 || y < 0 || x >= m || y >= n || parent >= mat[x][y]) return 1;
if(memo[x][y] != 0) return memo[x][y] + 1;
int best = 0;
for(int[] move : moves){
int count = dfs(x + move[0], y + move[1], mat[x][y]);
best = Math.max(best, count);
}
memo[x][y] = best;
return best + 1;
}
}