数组算法技巧

左右指针快慢指针

快慢指针技巧

有序数组去重

用到快慢指针技巧:

我们让慢指针 slow 走在后面,快指针 fast 走在前面探路,找到一个不重复的元素就赋值给 slow 并让 slow 前进一步。

这样,就保证了 nums[0..slow] 都是无重复的元素,当 fast 指针遍历完整个数组 nums 后,nums[0..slow] 就是整个数组去重之后的结果。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
int removeDuplicates(int[] nums){
if(nums.length == 0){
return 0;
}
int slow = 0,fast = 0;
while(fast < nums.length){
if(nums[fast] != nums[slow]){
slow++;
//维护 nums[0..slow] 无重复
nums[slow] = nums[fast];
}
fast++;
}
//数组长度为索引 + 1
return slow+1;
}

算法执行过程:

删除排序链表中的重复元素
1
2
3
4
5
6
7
8
9
10
11
12
13
ListNode deleteDuplicates(ListNode head){
if(head == null) return null;
ListNode slow = head,fast = head;
while(fast != null){
if(fast.val != slow.val){
slow.next = fast; //nums[slow] = nums[fast];
slow = slow.next; //slow++;
}
fast = fast.next; //fast++
}
slow.next = null;
return head;
}

算法执行过程:

有序数组/链表中去重

nums 中所有值为 val 的元素原地删除,依然需要使用快慢指针技巧:

如果 fast 遇到值为 val 的元素,则直接跳过,否则就赋值给 slow 指针,并让 slow前进一步。

1
2
3
4
5
6
7
8
9
10
11
12

int removeElement(int[] nums,int val){
int fast =0,slow =0;
while(fast < nums.length){
if(nums[fast] != val){
nums[slow] = nums[fast];
slow++;
}
fast++;
}
return slow;
}

将数组中的所有值为 0 的元素移到数组末尾

1
2
3
4
5
6
7
8
void moveZeroes(int[] nums){
//去除 nums 中的所有 0,返回不含 0 的数组长度
int p = removeElement(nums,0);
// 将 nums[p..] 的元素赋值为 0
for(;p<nums.length;p++){
nums[p] =0;
}
}

滑动窗口算法框架

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
void slidingWindow(string s,string t){
unordered_map<char,int> nedd,window;
for(char c: t){
need[c]++;
}
int left=0,right=0;
int valid =0;
while(right < s.size()){
char c = s[right];
//右移(增大)窗口
right++;
// 进行窗口内数据的一系列更新

while(window needs shrink){
char d = s[left];
// 左移(缩小)窗口
left++;
// 进行窗口内数据的一系列更新
}
}
}

左右指针的常用算法

二分查找

最简单的二分算法,旨在突出它的双指针特性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
int binarySearch(int[] nums,int target){
// 一左一右两个指针相向而行
int left = 0,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;
}

两数之和

只要数组有序,就应该想到双指针技巧。这道题的解法有点类似二分查找,通过调节 leftright 就可以调整 sum 的大小:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
int[] twoSum(int[] nums,int target){
// 一左一右两个指针相向而行
int left = 0,right =nums.length -1;
while(left < right){
int sum = nums[left] + nums[right];
if(sum == target){
// 题目要求的索引是从 1 开始的
return nwe int[]{left+1,right+1};
}else if(sum < target){
left++; //让 sum 大一点
}else if(sum > target){
right--; //让 sum 小一点
}
}
}

反转数组

1
2
3
4
5
6
7
8
9
10
11
12
void reverseString(char[] s){
// 一左一右两个指针相向而行
int left = 0,right = s.length-1;
while(left < right){
// 交换 s[left] 和 s[right]
char temp = s[left];
s[left] = s[right];
s[right] = temp;
left++;
right--;
}
}

回文串判断

1
2
3
4
5
6
7
8
9
10
11
12
boolean isPalindrome(String s){
// 一左一右两个指针相向而行
int left =0,right = s.length()-1;
while(left < right){
if(s.charAt(left) != s.charAt(right)){
return fasle;
}
left++;
right--;
}
return true;
}

找出最长的回文串

核心是从中心向两端扩散的双指针技巧
如果回文串的长度为奇数,则它有一个中心字符;如果回文串的长度为偶数,则可以认为它有两个中心字符。所以我们可以先实现这样一个函数:

1
2
3
4
5
6
7
8
9
10
11
//在 s 中寻找以 s[l] 和 s[r] 为中心的最长回文串
String palindrome(String s,int l,int r){
// 防止索引越界
while(l>=0 && r < s.lenth() && s.charAt(l) == s.charAt(r)){
// 双指针,向两边展开
l--;
r++;
}
// 返回以 s[l] 和 s[r] 为中心的最长回文串
return s.substring(l+1,r);
}

最长回文串的问题,解法的大致思路就是:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
for 0 <= i < len(s):
找到以 s[i] 为中心的回文串
找到以 s[i] 和 s[i+1] 为中心的回文串

String longestPalindrome(String s) {
String res = "";
for (int i = 0; i < s.length(); i++) {
// 以 s[i] 为中心的最长回文子串
String s1 = palindrome(s, i, i);
// 以 s[i] 和 s[i+1] 为中心的最长回文子串
String s2 = palindrome(s, i, i + 1);
// res = longest(res, s1, s2)
res = res.length() > s1.length() ? res : s1;
res = res.length() > s2.length() ? res : s2;
}
return res;
}