刷题日记 - 5
记录刷题。
难度:中等
双指针
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| class Solution { public int[] twoSum(int[] numbers, int target) { int[] res = new int[2]; int head = 0; int tail = numbers.length - 1;
while (head < tail && (numbers[head] + numbers[tail]) != target) { if (numbers[head] + numbers[tail] < target) { head++; } else { tail--; } }
res[0] = head + 1; res[1] = tail + 1;
return res; } }
|
利用数组的有序性,头尾两个指针,根据当前两个指针指向的数的和与目标值的关系,调整指针的位置。非常简单。
难度:中等
双指针
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
| class Solution { public int minSubArrayLen(int target, int[] nums) { int INF = Integer.MAX_VALUE; int len = nums.length; int p = 0; int q = 0; int windowVal = 0; int res = INF;
while (p < len) { windowVal += nums[p]; while (windowVal >= target) { res = Math.min(res, p - q + 1); windowVal -= nums[q]; q++; } p++; }
return res == INF ? 0 : res; } }
|
用滑动窗口的思想解决,也是比较简单。
确保两个指针q
和p
在正确的时机移动,并且维护一个windowVal
记录当前窗口[q,p]
中所有元素的和。
在每一次窗口满足条件(窗口内元素和大于等于目标值)时,需要及时更新最小窗口长度res
难度:困难
滑动窗口
难点在于,1 <= words.length <= 5000
如何判断滑动窗口中的子串是串联子串呢。
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 46
| class Solution { public List<Integer> findSubstring(String s, String[] words) { List<Integer> res = new ArrayList<>(); int len = s.length(); HashMap<String, Integer> patternMap = new HashMap<>(); for (String word : words) { patternMap.put(word, patternMap.getOrDefault(word, 0) + 1); } int STEP = words[0].length(); int SIZE = words.length * STEP;
for (int i = 0; i < STEP; i++) { int idx = i; int p = i; HashMap<String, Integer> windowMap = new HashMap<>(); while (p + STEP <= len) { String cur = s.substring(p, p + STEP); if (patternMap.containsKey(cur)) { windowMap.put(cur, windowMap.getOrDefault(cur, 0) + 1); while (windowMap.get(cur) > patternMap.get(cur)) { String tmp = s.substring(idx, idx + STEP); idx += STEP; windowMap.put(tmp, windowMap.get(tmp) - 1); } if (p - idx + STEP == SIZE) { res.add(idx); String tmp = s.substring(idx, idx + STEP); windowMap.put(tmp, windowMap.get(tmp) - 1); idx += STEP; } p += STEP; } else { windowMap.clear(); p += STEP; idx = p; } } } return res; } }
|
判断是否是串联子串
使用两个HashMap
来记录。前一个记录words[]
中出现的所有单词的频率。后一个维护窗口中的单词频率。
窗口尾部指针所指的单词是否在words[]
中,即是否是需要的单词。
- 如果是,将该单词频率在第二个哈希表中加一,然后进行下面的步骤之后将窗口尾部指针指向下一个单词
- 当窗口内的某个单词频率超过了其在
words[]
中的出现频率,那么就需要收缩窗口,也就是移动窗口头部指针,直至这个单词的频率重新等于words[]
(在窗口内的单词都是属于words[]
中的,所以一定在哈希表中)
- 如果一切正常,没有单词频率超标,就查看一下当前的窗口长度是否等于标准的串联子串的长度,如果等于,则记录当前的窗口头部,并且将窗口头部指针往前移动一个单词,寻找下一个idx。
- 如果不是,那么从窗口头部到窗口尾部指针这一段都不能作为串联子串的开始,所以这里就要找到下一个需要的单词(while循环寻找),然后重置窗口头部和尾部指针到这个单词的起始位置。
⭐注意:根据每个单词的长度,需要进行改变起始点:
1 2 3 4
| for (int i = 0; i < STEP; i++) { int idx = i; int p = i;
|
我做的时候,就忽略了这一点。。。我只从idx=0
寻找,导致结果中的idx
全是STEP
的倍数。
本题和这个题76. 最小覆盖子串 - 力扣(LeetCode)挺像的,这个题cnt[]
就相当于是本题的patternMap
,这个题的用count
来判断当前串是否是覆盖子串,而本题用SIZE
。
难度:中等
矩阵
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 { public boolean isValidSudoku(char[][] board) { int[][] row = new int[9][9]; int[][] col = new int[9][9]; int[][] block = new int[9][9];
int m = board.length; int n = board[0].length; boolean res = true;
for(int i = 0; i < m; i++) { for(int j = 0; j < n; j++) { char curChar = board[i][j]; if(curChar == '.') continue; int cur = board[i][j] - '1'; int blockIdx = (i / 3) * 3 + j / 3; if(row[i][cur] == 1 || col[j][cur] == 1 || block[blockIdx][cur] == 1) { res = false; break; } row[i][cur] = 1; col[j][cur] = 1; block[blockIdx][cur] = 1; } if(!res) break; } return res; } }
|
用这种方法只需要遍历一次board[]
即可。
记录每一行,每一列,每一个3x3方块(将[i,j]
映射到blockIdx
)中数字是否出现。
难度:中等
矩阵
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
| class Solution { public void gameOfLife(int[][] board) { int m = board.length; int n = board[0].length;
int[][] state = new int[m][n];
for(int i = 0; i < m; i++){ for(int j = 0; j < n; j++){ if(board[i][j] == 0) continue; if((0 <= i - 1) && (0 <= j - 1)) state[i - 1][j - 1] += 1; if(0 <= i - 1) state[i - 1][j] += 1; if((0 <= i - 1) && (n > j + 1)) state[i - 1][j + 1] += 1; if(0 <= j - 1) state[i][j - 1] += 1; if(n > j + 1) state[i][j + 1] += 1; if((m > i + 1) && (0 <= j - 1)) state[i + 1][j - 1] += 1; if(m > i + 1) state[i + 1][j] += 1; if((m > i + 1) && (n > j + 1)) state[i + 1][j + 1] += 1; } }
for(int i = 0; i < m; i++){ for(int j = 0; j < n; j++){ if(board[i][j] == 1){ if(state[i][j] < 2) board[i][j] = 0; if(state[i][j] > 3) board[i][j] = 0; } else { if(state[i][j] == 3) board[i][j] = 1; } } } } }
|
需要遍历board[][]
两次,第一次遍历的时候更新 邻居 的状态,每个细胞有八个邻居,如果当前细胞是活细胞则给八个邻居的状态都加一,死细胞则掠过。
第二次遍历的时候,根据当前位置的邻居状态和当前位置的细胞状态更新当前位置的细胞存活情况。
难度:简单
哈希表
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 { public boolean canConstruct(String ransomNote, String magazine) { boolean res = true; int[] cnt = new int[26]; int[] need = new int[26];
for(char ch : ransomNote.toCharArray()) { need[ch - 'a']++; }
for(char ch : magazine.toCharArray()) { cnt[ch - 'a']++; }
for(int i = 0; i < 26; i++) { if(need[i] > cnt[i]) { res = false; break; } }
return res; } }
|
用数组模拟哈希表,因为'a'<key<'z'
,数组中对应的值为该字母在magazine
中出现的次数,如果ransomeNote
中某个字母出现次数都小于等于其在magazine
中的出现次数,就表明可以写成这个赎金信。