数组问题、快慢指针、双指针刷题小结
- 原地修改的要求
- 一般说到**'必须只使用常量级的额外空间"**要求,就是需要用指针来解决不能新建数组
- 快慢指针一定要各司其职,慢指针负责控制有效数组,快指针负责在后边进行遍历
- 一些基本用法要回顾
- charAt()
- substring()
- length()
- String一定要大写
- 返回数组格式时的定义方法: return new int[]{left + 1, right + 1};
26. 删除有序数组中的重复项(一次AC)
- 初步思路,遍历数组并存入到hash表中,用新的数组存储return的数组,如果有重复,则跳过这一项,最后返回新的数组
- 初步思路G:原地修改原来是不让new数组,只能在原数组上操作,返回结果为数组的前k项
- 二步思路就是,用两个指针,分别指向第一个元素a和比该元素大的下一个元素b,将b放在a的下一个位置然后a++, b++,继续判断,如果小于等于就跳过,最后a停下的位置就是有效数组的长度
- 二步思路 == 题解
class Solution {
public int removeDuplicates(int[] nums) {
int slow = 0;
int fast = 0;
while(fast<nums.length){
if (nums[slow]==nums[fast]){
fast++;
}
else{
nums[slow+1] = nums[fast];
slow++;
fast++;
}
}
return slow+1;
}
}
83. 删除排序链表中的重复元素
- Java/Python 这类带有垃圾回收的语言,可以帮我们自动找到并回收这些「悬空」的链表节点的内存, 不需要释放
- 这里链表要比数组麻烦一点,就是链表返回的是头节点,而不是一个带长度的数组,数组的话很确定的知道有多少元素是最终结果,但是链表注意一定要把链表尾部清理干净,不要留下一些需要替换的元素但是没有删除或者没有处理
- 另外,先 fast = fast.next;,再slow.next = null; 非常重要,如果顺序颠倒,那么fast和slow此刻指向同样的位置,slow经过null之后就断掉了!!!
class Solution {
public ListNode deleteDuplicates(ListNode head) {
ListNode slow = new ListNode();
ListNode fast = new ListNode();
slow = head;
fast = head;
while(fast!=null){
if(slow.val == fast.val){
fast = fast.next;
slow.next = null;
}
else{
slow.next = fast;
slow = slow.next;
fast = fast.next;
slow.next = null;
}
}
return head;
}
}
27. 移除元素
-
一开始思路就跟shit一样,一直在考虑对于该位置是val的情况下怎么处理,双指针的用法就是用slow记录是val的位置然后用fast记录不是val的位置,用fast来替换slow。但是用双指针指了半天还是很混乱
- 我考虑如果两个相连位置都是val怎么办,那我用fast代替了第一个位置的val,fast处的值要如何替换,这个值已经不能再被存储一遍了,但是这个值改成val那么轮到它的时候还需要再处理一遍
- fast已经遍历到第二个val后边了,而我设计的流程中其实只能依靠fast来找是val的位置,所以此刻fast只能移动到和slow相同的位置,也就是还需要退回去
- 返回长度也很难计算,因为slow处理完当前为val的地方就会被置为-1,而fast会一直到结尾
-
看思路后一句话点醒
- 我最后只需要返回需要的值,那我只需要把需要的值找到,然后挨个放置到当前可以放置的地方即可
- 对于那些val的位置,我完全可以看作为空,类似于我把空去掉的一道题,正常做法就是把值往前挪
- 我的思路最错误的一点就是为了省事,所以把slow变成一个标记的指针,而没有挨个挪动,这样的话fast又需要找当前的位置,还需要探测后边的值,一直需要回退,效率非常低!!!
- 双指针的要点就是:各自分工,slow一直指向当前已经处理好的最后位置,fast用于探索
class Solution {
public int removeElement(int[] nums, int val) {
int slow = 0;
int fast = 0;
while(fast<nums.length){
if(nums[fast]!=val){
nums[slow] = nums[fast];
slow++;
fast++;
}
else{
fast++;
}
}
return slow;
}
}
283. 移动零 (一次AC)
class Solution {
public void moveZeroes(int[] nums) {
int fast = 0;
int slow = 0;
while(fast<nums.length){
if(nums[fast]==0){
fast++;
}
else{
nums[slow] = nums[fast];
slow++;
fast++;
}
}
if (slow<nums.length){
for(int i=slow;i<nums.length;i++){
nums[i] = 0;
}
}
}
}
167. 两数之和 II - 输入有序数组
- 双指针逼近的思路
class Solution {
public int[] twoSum(int[] numbers, int target) {
int left = 0;
int right = numbers.length-1;
while(left<right){
int sum = numbers[left] + numbers[right];
if(sum==target){
return new int[]{left + 1, right + 1};
}
else if(sum<target){
left++;
}
else{
right--;
}
}
return new int[] {-1,-1};
}
}
344. 反转字符串
class Solution {
public void reverseString(char[] s) {
int left = 0;
int right = s.length-1;
while(left<right){
char temp;
temp = s[left];
s[left] = s[right];
s[right] = temp;
left++;
right--;
}
}
}
5. 最长回文子串(之前的思路是dp,现在用双指针试一下子)
- 字符串中,s.length()要加括号,并且在取某个值的时候,需要用charAt()
- s.substring 包括前不包括后边
- 一直纠结一个点就是palindrome(s, i,i+1) 传入的话,i如果是最后一位,那么i+1就越界了,在palindrome函数中会return s.substring(l + 1, r); 不就会越界报错么,但是 s.substring 包括前不包括后边,所以如果是s.substring(1000, 1000),里边的值无论多大都是无效的,不会报错
class Solution {
public String longestPalindrome(String s) {
String p = "";
for (int i=0;i<s.length();i++){
// 判断是否满足奇数回文
String p1 = palindrome(s, i,i);
// 判断满足偶数回文不
String p2 = palindrome(s, i,i+1);
p = p1.length()>p.length()? p1:p;
p = p2.length()>p.length()? p2:p;
}
return p;
}
public String palindrome(String s, int l, int r){
while(l>=0 && r<s.length()){
if(s.charAt(l)==s.charAt(r)){
l--;
r++;
}
else{
break;
}
}
return s.substring(l + 1, r);
}
}