Leetcode记录库解题方法篇之二:双指针
简介
七、双指针
结论
一些很主观的东西,归根到底可能还是自己的实力不够:
1 167. 两数之和 II - 输入有序数组
思路描述
- 由于初始数组是有序的,所以我们可以使用双指针,左右搜索。
- 如果当前的两个索引值之和小于target,我们就右移左索引
- 如果大于target,我们就左移右索引
- 相等我们就返回。
代码实现
java代码:
class Solution {
public int[] twoSum(int[] numbers, int target) {
int[] ans = new int[2];
int idx = 0, n = numbers.length - 1, tmp;
while (idx < n) {
tmp = numbers[idx] + numbers[n];
if (tmp == target) {
ans[0] = idx + 1;
ans[1] = n + 1;
return ans;
} else if (tmp < target) idx++;
else n--;
}
return ans;
}
}
注意事项
- 注意事项
拓展延伸
- 拓展延伸
2 633. 平方数之和
思路描述
- 由于初始数组是有序的,所以我们可以使用双指针,左右搜索。
- 如果当前的两个索引值之和小于target,我们就右移左索引
- 如果大于target,我们就左移右索引
- 相等我们就返回。
代码实现
java代码:
class Solution {
public boolean judgeSquareSum(int c) {
long sqrtC = (int)Math.sqrt(c);
long a = 0, b = sqrtC, tmp = 0;
while (a <= b) {
tmp = a * a + b * b;
if (tmp == c) return true;
else if (tmp < c) a++;
else b--;
}
return false;
}
}
注意事项
- 注意事项
拓展延伸
- 拓展延伸
3 345. 反转字符串中的元音字母
思路描述
- 双指针前后扫
- 虽然想到了可以用 set 把原因字符存起来,然后判断 s 中的字符是不是元音,这样就不会看起来这么一大片了,也不会进行多次判断,可能速度也会快点。但是实际操作起来发现速度没有这样快。
- 时间复杂度O(1/2 * n),空间复杂度O(2n)
代码实现
java代码:
class Solution {
public String reverseVowels(String s) {
int h = 0, t = s.length() - 1;
char tmp;
char[] strArray = s.toCharArray();
while(h < t){
if(strArray[h] == 'a' || strArray[h] == 'e' || strArray[h] == 'i' || strArray[h] == 'o' || strArray[h] == 'u' ||
strArray[h] == 'A' || strArray[h] == 'E' || strArray[h] == 'I' || strArray[h] == 'O' || strArray[h] == 'U'){
if(strArray[t] == 'a' || strArray[t] == 'e' || strArray[t] == 'i' || strArray[t] == 'o' || strArray[t] == 'u' ||
strArray[t] == 'A' || strArray[t] == 'E' || strArray[t] == 'I' || strArray[t] == 'O' || strArray[t] == 'U'){
tmp = strArray[h];
strArray[h] = strArray[t];
strArray[t] = tmp;
h++;
t--;
} else {
t--;
}
} else {
h++;
}
}
return new String(strArray);
}
}
- 使用 set 判断是不是元音,速度没有上面的直观写法快。
class Solution {
public String reverseVowels(String s) {
HashSet<Character> set = new HashSet<>(Arrays.asList('a', 'e', 'i', 'o', 'u', 'A', 'E', 'I', 'O', 'U'));
int h = 0, t = s.length() - 1;
char tmp;
char[] strArray = s.toCharArray();
while(h < t){
if(set.contains(strArray[h])){
if(set.contains(strArray[t])){
tmp = strArray[h];
strArray[h] = strArray[t];
strArray[t] = tmp;
h++;
t--;
} else {
t--;
}
} else {
h++;
}
}
return new String(strArray);
}
}
- 这样写也可以起到set的作用
class Solution {
public String reverseVowels(String s) {
String vowels = new String("aeiouAEIOU");
int h = 0, t = s.length() - 1;
char tmp;
char[] strArray = s.toCharArray();
while(h < t){
if (vowels.indexOf(strArray[h]) > -1) {
if (vowels.indexOf(strArray[t]) > -1) {
tmp = strArray[h];
strArray[h] = strArray[t];
strArray[t] = tmp;
h++;
t--;
} else {
t--;
}
} else {
h++;
}
}
return new String(strArray);
}
}
注意事项
- 注意事项
拓展延伸
- 难道就没有办法直接在原始的字符串上做操作吗?只能新建一个?
4 680. 验证回文字符串 Ⅱ
思路描述
- 双指针前后扫,如果没有遇到不相同的字符直接返回true;
- 第一次遇到不同的字符,就头后移一位 || 尾前移一位,重新开始判断,再次遇到不一样的字符就返回false
- 如果没有遇到就返回true。
- 时间复杂度O(n),空间复杂度O(1)
代码实现
java代码:
class Solution {
public boolean validPalindrome(String s) {
return validPalindrome(s, 0, s.length() - 1, 1);
}
private boolean validPalindrome(String s, int h, int t, int del){
while(h < t) {
// System.out.print(s.charAt(h));System.out.println(s.charAt(t));
if (s.charAt(h) == s.charAt(t)) {
h++;
t--;
} else if (del == 1){
del = 0;
return validPalindrome(s, h + 1, t, del) || validPalindrome(s, h, t - 1, del);
} else return false;
}
return true;
}
}
注意事项
- 注意事项
拓展延伸
- 注意事项
5 88. 合并两个有序数组
思路描述
- 先把大数组中的数据移到后半部分,然后在归并两个有序数组到大数组的前部。
- 时间复杂度O(m + n),空间复杂度O(1)
代码实现
java代码:
class Solution {
public void merge(int[] nums1, int m, int[] nums2, int n) {
int i = 0, i1 = n, i2 = 0, m2 = m - 1;
int tmp;
while (m2 > -1) {
nums1[m2 + n] = nums1[m2--];
}
while(i1 < m + n && i2 < n) {
if (nums1[i1] < nums2[i2]) {
nums1[i++] = nums1[i1++];
} else {
nums1[i++] = nums2[i2++];
}
}
while (i1 < m + n) {
nums1[i++] = nums1[i1++];
}
while (i2 < n) {
nums1[i++] = nums2[i2++];
}
}
}
注意事项
- 注意事项
拓展延伸
- 注意事项
6 141. 环形链表
思路描述
- 快慢指针
- 时间复杂度O(m + n),空间复杂度O(1)
代码实现
java代码:
public class Solution {
public boolean hasCycle(ListNode head) {
if (head == null || head.next == null) return false;
ListNode fast = head.next, slow = head;
while (fast != slow && fast != null && fast.next != null) {
fast = fast.next.next;
slow = slow.next;
}
if (fast == slow) return true;
else return false;
}
}
注意事项
- 循环条件和循环体的判断,再循环条件中一定要把fast.next.next之前的两个fast和fast.next都要判断了。
while (fast != slow && fast != null && fast.next != null) {
fast = fast.next.next;
slow = slow.next;
}
拓展延伸
- 注意事项
7 524. 通过删除字母匹配到字典里最长单词
思路描述
- 快慢指针
- 时间复杂度O(m + n),空间复杂度O(1)
代码实现
java代码:
class Solution {
public String findLongestWord(String s, List<String> dictionary) {
int maxl = 0, i, j;
String res = "";
for (String d : dictionary) {
i = 0;
j = 0;
while (i < s.length() && j < d.length()) {
if (s.charAt(i) == d.charAt(j)) {
j++;
}
i++;
}
if (j == d.length()) {
if (maxl < j || (maxl == j && res.compareTo(d) > 0)) {
maxl = j;
res = d;
}
}
}
return res;
}
}
注意事项
- 注意事项
拓展延伸
- 注意事项
8 剑指 Offer 57 - II. 和为s的连续正数序列
思路描述
- 一旦涉及到求一个范围、一段连续的元素的问题,考虑使用双指针
- 时间复杂度O(n),空间复杂度O(1)
代码实现
java代码:
class Solution {
public int[][] findContinuousSequence(int target) {
int halftarget = target >> 1;
int l = 1, r = 1, sum = 0;
ArrayList<int[]> ans = new ArrayList<>();
while (l <= halftarget) {
if (sum == target) {
int[] tmp = new int[r - l];
for (int i = l, j = 0; i < r; i++, j++) {
tmp[j] = i;
}
ans.add(tmp);
sum -= l;
l++;
} else if (sum < target) {
sum += r;
r++;
} else {
sum -= l;
l++;
}
}
return ans.toArray(new int[ans.size()][]);
}
}
注意事项
- 注意事项
拓展延伸
1.
// Integer 范型的 List 转化成 int[] 的方法
list1.stream().mapToInt(Integer::valueOf).toArray()
9 42. 接雨水
思路描述
- 单调栈
- 时间复杂度O(n),空间复杂度O(n)
代码实现
java代码:
class Solution {
public int trap(int[] height) {
int ans = 0, n = height.length;
Deque<Integer> s = new LinkedList<>(); // 注意!!!里面放的时索引!!!
for (int i = 0; i < n; i++) {
while (!s.isEmpty() && height[i] > height[s.peek()]) { // 如果当前要进栈的高度大于栈顶,那么就可能形成一个凹槽:高 低 高
int low = s.pop(); // 凹槽的底部高度,我们下面称它为“低”
if (s.isEmpty()) { // 如果栈中此时没有元素了,那么也就构不成凹槽了,只有:低 高
break; // 中断
}
int l = s.peek(); // 如果栈中此时还有元素,查看这个元素的高度
if (height[l] == height[low]) { // 如果这个元素的高度和低一样高,那么说明此时的凹槽张这样:(未知)低低高
continue; // 我们提前开启下一次循环,寻找左边的高度
} else { // 如果这个元素的高度比:低 高
int w = i - l - 1; // 那么我们计算这个凹槽的宽度
int h = Math.min(height[i], height[l]) - height[low]; // 计算这个凹槽的高度:两个较矮的高边 - 凹槽的低
ans += w * h; // 计算容量,累加
}
}
s.push(i); // 新高度入栈
}
return ans;
}
}
思路描述
- 双指针,这个有点类似于11. 盛最多水的容器
- 时间复杂度O(n),空间复杂度O(1)
代码实现
java代码:
class Solution {
public int trap(int[] height) {
int l = 0, r = height.length - 1, ans = 0;
int lmax = height[0], rmax = height[r];
while (l < r) {
lmax = Math.max(lmax, height[l]); // 更新左边的高边
rmax = Math.max(rmax, height[r]); // 更新右边的高边
if (height[l] < height[r]) { // 如果左边现在的高度小于右边,那么现在至少出现了这个样一个凹槽 lmax...height[l] ... height[r],并且height[r]也是一定高于lmax的(自己想想,或者模拟以下,这个不太好用文字说明)
ans += lmax - height[l]; // 求这个凹槽能称多少水
l++;
} else {
ans += rmax - height[r];
r--;
}
}
return ans;
}
}
注意事项
- 注意事项
拓展延伸
- a
10 76. 最小覆盖子串
思路描述
- 双指针,滑动窗口,计数法
- 时间复杂度O(n),空间复杂度O(1)
代码实现
java代码:
class Solution {
public String minWindow(String s, String t) {
int slen = s.length(), tlen = t.length();
if (slen < tlen) {
return "";
}
int[] need = new int[128]; // 桶,用来记录目标串t中各个字符的数量,表示我们需要找到多少对应的字符来覆盖目标串
for (int i = 0; i < tlen; i++) {
need[t.charAt(i)]++;
}
int l = 0, r = 0, cnt = tlen; // cnt 是我们需要找到的目标字符数量
int min = slen + 1, start = 0, end = 0;
while (r < slen) {
while (cnt != 0 && r < slen) {
if (need[s.charAt(r)] > 0) { // 如果字符串s当前的字符和目标中的的一致
cnt--; // 找到了一个覆盖字符,需要的字符减一
}
need[s.charAt(r)]--; // 对应的 需要字符 的桶中数量减一,即使是不需要的字符也会减一
r++; // 右移右指针
}
while (l < r && cnt == 0) { // 移动左指针缩小滑动窗口的大小
if (need[s.charAt(l)] < 0) { // 如果当前的字符不是覆盖字符中的一个,说明可以向右移动左指针
need[s.charAt(l)]++; // 窗口缩小,need中的字符也要对应变化
l++; // 右移指针
} else { // 如果当前扫描到的字符是覆盖字符中的一个,说明此时在缩小窗口的话,就不能够再覆盖目标串了
if (min > r - l) { // 判断当前的窗口大小和之前记录的窗口大小关系
min = r - l;
start = l;
end = r;
}
need[s.charAt(l)]++; // 窗口变小,没有被窗口覆盖的字符增多
l++; // 左指针右移
cnt++; // 需要的字符个数 + 1
break; // 此时已经窗口中的字符已经覆盖不了目标串了,再遍历也没有用,不如重新右移右指针,遍历未搜索的s中的字符
}
}
}
return s.substring(start, end);
}
}
注意事项
- 注意事项
拓展延伸
- a
11 2024. 考试的最大困扰度
思路描述
代码实现
java代码:
// 字符串的问题,想到了用动态规划(行不行呢?好像不行,还得分情况记录状态),但是忘了滑动窗口
// 滑动窗口离不了双指针
class Solution {
public int maxConsecutiveAnswers(String answerKey, int k) {
int len = answerKey.length();
if (len < 2) return len; // 特殊情况判断
return Math.max(getMax(answerKey, k, 'T'), getMax(answerKey, k, 'F')); // 可能是连续的 T 也可能是连续的 F
}
private int getMax(String answerKey, int k, char flag) {
int l = 0, r = 0; // 双指针
int max = 1, cnt = 0, len = answerKey.length();
while (r < len) {
cnt += answerKey.charAt(r) == flag ? 0 : 1; // 记录窗口内和目标字符 flag 不同的字符个数,使用 k 次修改机会对其进行修改
while (cnt > k) { // 如果当前窗口内记录的和 flag 字符不同的字符个数大于了 k,此时需要缩小滑动窗口了,一直到 cnt == k 停止
cnt -= answerKey.charAt(l++) == flag ? 0 : 1;
}
max = Math.max(max, r - l + 1); // 更新最大长度
r++;
}
return max;
}
}
注意事项
- 注意事项
拓展延伸
- a