目录
① 快慢指针:
一. 删除有序数组中的重复项
1. 思路和代码
I. 博主的做法:
- slow充当指针,如果后面的值和slow指向的值不一样,那么就在slow的下一位置保存这个新的值(),同时更新slow指针。这样巧妙的节省了fast这个指针。
class Solution {
public int removeDuplicates(int[] nums) {
int slow = 0;
int length = nums.length;
for(int i = 1; i < nums.length; i++){
if(nums[i] == nums[slow])
length--;
else
nums[++slow] = nums[i];
}
return length;
}
}
- 这里的lenth完全可以不定义,直接返回slow+1就可以,也表示的是数组的长度。
- 修改后代码为:
class Solution {
public int removeDuplicates(int[] nums) {
if(nums.length == 0)
return 0;
int slow = 0;
for(int i = 1; i < nums.length; i++)
if(nums[i] != nums[slow])
nums[++slow] = nums[i];
return slow + 1;
//return ++slow
}
}
- 这里不能return slow++,因为这样slow还没更新,就被返回了。也可以return ++slow。
II. 东哥的做法:
- 我们让慢指针 slow 走在后面,快指针 fast 走在前面探路,找到一个不重复的元素就赋值给 slow 并让 slow 前进一步。这样,就保证了 nums[0…slow] 都是无重复的元素,当 fast 指针遍历完整个数组 nums 后,nums[0…slow] 就是整个数组去重之后的结果。
- 这里,fast相当于我思路中循环中的i。
class Solution {
public int removeDuplicates(int[] nums) {
if(nums.length == 0)
return 0;
int fast = 0;
int slow = 0;
while(fast < nums.length){
if(nums[fast] != nums[slow])
nums[++slow] = nums[fast];
fast++;
}
return slow + 1;
}
}
2. 总结
- 简单的快慢指针问题,没什么可说的。
二. 删除排序链表中的重复元素(扩展)
1. 思路和代码
I. 博主的做法:
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/
class Solution {
public ListNode deleteDuplicates(ListNode head) {
if(head == null)
return null;
ListNode slow = head;
for(ListNode i = slow; i != null; i = i.next)
if(slow.val != i.val){
slow.next = i;
slow = slow.next;
}
slow.next = null;
return head;
}
}
II. 东哥的做法:
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/
class Solution {
public ListNode deleteDuplicates(ListNode head) {
if (head == null) return null;
ListNode slow = head, fast = head;
while (fast != null) {
if (fast.val != slow.val) {
// nums[slow] = nums[fast];
slow.next = fast;
// slow++;
slow = slow.next;
}
// fast++
fast = fast.next;
}
// 断开与后面重复元素的连接
slow.next = null;
return head;
}
}
-
这里可能有读者会问,链表中那些重复的元素并没有被删掉,就让这些节点在链表上挂着,合适吗?
- 这就要探讨不同语言的特性了,像 Java/Python 这类带有垃圾回收的语言,可以帮我们自动找到并回收这些「悬空」的链表节点的内存,而像 C++ 这类语言没有自动垃圾回收的机制,确实需要我们编写代码时手动释放掉这些节点的内存。
2. 总结
- 思路和数组的差不多,不再赘述。
三. 移除元素
1. 思路和代码
I. 博主的做法:
- 若寻找的元素和需要删除的值相等:
- 并且这个元素是第一个与删除值相等的元素,那么用temp保存此元素下标;否则,直接跳过这个元素。
- 不相等:
- 用后面的元素依次覆盖前面的元素,达到删除的目的。
class Solution {
public int removeElement(int[] nums, int val) {
if(nums.length == 0)
return 0;
int temp = 0;
boolean flag = false;
for(int i = 0; i < nums.length; i++){
if(nums[i] == val){
if(flag == false){
temp = i;
flag = true;
}
else continue;
}
else
nums[temp++] = nums[i];
}
return temp;
}
}
- else continue;这一行可以优化掉。
- 想了想,flag也可以给它优化掉,直接用temp代替,本质上就是初值设定的比较离谱(只要是负数都可以),让temp充当flag的角色。修改后,代码如下:
class Solution {
public int removeElement(int[] nums, int val) {
if(nums.length == 0)
return 0;
int temp = -1;
for(int i = 0; i < nums.length; i++){
if(nums[i] == val && temp == -1)
temp = i;
else if(nums[i] != val)
nums[temp++] = nums[i];
}
return temp;
}
}
II. 东哥的做法:
- 如果 fast 遇到值为 val 的元素,则直接跳过,否则就赋值给 slow 指针,并让 slow 前进一步。
class Solution {
public int removeElement(int[] nums, int val) {
if(nums.length == 0)
return 0;
int slow = 0;
for(int i = 0; i < nums.length; i++){
if(nums[i] != val)
nums[slow++] = nums[i];
}
return slow;
}
}
class Solution {
public int removeElement(int[] nums, int val) {
if(nums.length == 0)
return 0;
int slow = 0;
int fast = 0;
while(fast < nums.length){
if(nums[fast] != val)
nums[slow++] = nums[fast];
fast++;
}
return slow;
}
}
- 注意这里和有序数组去重的解法有一个细节差异,我们这里是先给 nums[slow] 赋值然后再给 slow++,这样可以保证 nums[0…slow-1] 是不包含值为 val 的元素的,最后的结果数组长度就是 slow。
2. 总结
- 这题和上面那个思路差不多,是我想复杂了。东哥的思路有点像是把原来的数组当成了一个新数组,用fast进行遍历,如果不等于val,就把这个元素添加到这个新的数组中。这样想,就完全不需要知道这个元素是不是第一个等于val的元素。牛逼plus!
四. 移动零
1. 思路和代码
I. 博主的做法:
- 本质上就是删除0,然后在数组的末尾去补0。
class Solution {
public void moveZeroes(int[] nums) {
if(nums.length == 0)
System.out.print(0);
int slow = 0;
for(int i = 0; i < nums.length; i++){
if(nums[i] != 0)
nums[slow++] = nums[i];
}
for(int i = slow; i < nums.length; i++)
nums[i] = 0;
for(int i = 0; i < nums.length; i++){
if(i == nums.length - 1)
System.out.print(nums[i]);
else
System.out.print(nums[i] + ",");
}
}
}
- 本来想着,是不是可以不改变数组,直接通过打印AC这道题呢,写了如下代码:
class Solution {
public static void moveZeroes(int[] nums) {
if(nums.length == 0)
System.out.print(0);
int count = 0;
for(int i = 0; i < nums.length; i++){
if(nums[i] != 0){
count++;
System.out.print(nums[i]);
if(count != nums.length)
System.out.print(",");
}
}
for(int i = count; i < nums.length; i++){
System.out.print(0);
if(i != nums.length - 1)
System.out.print(",");
}
}
}
结果:
看来leetcode的输出,是遍历了这个数组,氧化钙。
II. 东哥的做法:
class Solution {
public static int removeElement(int[] nums, int val) {
if(nums.length == 0)
return 0;
int slow = 0;
int fast = 0;
while(fast < nums.length){
if(nums[fast] != val)
nums[slow++] = nums[fast];
fast++;
}
return slow;
}
public static void moveZeroes(int[] nums) {
// 去除 nums 中的所有 0,返回不含 0 的数组长度
int p = removeElement(nums, 0);
// 将 nums[p..] 的元素赋值为 0
for (; p < nums.length; p++) {
nums[p] = 0;
}
}
}
- 题目让我们将所有 0 移到最后,其实就相当于移除 nums 中的所有 0,然后再把后面的元素都赋值为 0 即可。
2. 总结
- 东哥的思路和博主是一样的,我的代码中的count就相当于slow指针。要像东哥一样,擅于去进行题目的整合。
② 左右指针
一. 二分查找
- 无。函数名为:public static int binarySearch(int[] nums, int target);
1. 思路和代码
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;
}
- “=”存在的意义:如果数组元素只有一个的话,就存在left == right的情况
二. 两数之和 II - 输入有序数组
1. 思路和代码
I. 博主的做法:
- 先暴力一波,有点像是握手问题。
class Solution {
public int[] twoSum(int[] numbers, int target) {
int[] res = new int[2];
for(int i = 0; i < numbers.length; i++){
for(int j = i + 1; j < numbers.length; j++){
if(numbers[i] + numbers[j] == target){
res[0] = i + 1;
res[1] = j + 1;
return res;
}
}
}
return res;
}
}
II.东哥的做法:
- 只要数组有序,就应该想到双指针技巧。这道题的解法有点类似二分查找,通过调节 left 和 right 就可以调整 sum 的大小。
class Solution {
public int[] twoSum(int[] numbers, int target) {
int left = 0;
int right = numbers.length - 1;
while(left < right){
if(numbers[left] + numbers[right] == target)
return new int[]{left + 1, right + 1};
else if(numbers[left] + numbers[right] > target)
right--;
else
left++;
}
return new int[]{-1, -1};
}
}
- 题目给的是一个有序数组,如果两个元素的和 > target,明显是右边的元素太大了,也就是说要缩小一些;如果是两个元素的和 < target,那就是左边的元素太小了,要放大一些。
2. 总结
- 只要数组有序,就应该想到双指针技巧!!!
三. 反转字符串
1. 思路和代码
I. 博主的做法:
- 很简单嘛,就是整一个临时变量temp保存字符,然后互换位置。
class Solution {
public void reverseString(char[] s) {
int left = 0;
int right = s.length - 1;
char temp;
while(left < right){
temp = s[left];
s[left] = s[right];
s[right] = temp;
left++;
right--;
}
}
}
II.东哥的做法:
- 和博主想的一样。
四. 回文串判断
- 无。函数名为:public static boolean isPalindrome(String s);
1. 思路和代码
I. 博主的做法:
public static boolean isPalindrome(String s){
int left = 0;
int right = s.length() - 1;
while(left < right){
if(s.charAt(left) != s.charAt(right))
return false;
left++;
right--;
}
return true;
}
- 和上一个交换数组基本上一模一样,换汤不换药。
II.东哥的做法:
- 和博主想的一样。
五. 最长回文子串
1. 思路和代码
I. 博主的做法:
- 复用了上面判断是否为回文数的算法。本质上还是暴力,使用substring方法,从最长的长度开始构建字符串。
- 先固定子字符串的长度,然后不断的向右移动字符串的起点,依次对得到的子字符串进行判断,若是回文串,直接进行输出。
class Solution {
public static boolean isPalindrome(String s){
int left = 0;
int right = s.length() - 1;
while(left < right){
if(s.charAt(left) != s.charAt(right))
return false;
left++;
right--;
}
return true;
}
public String longestPalindrome(String s) {
int len = s.length();
String str;
while(len > 0){
for(int i = 0; i < s.length(); i++){
if(i + len <= s.length()){
str = s.substring(i, i + len);
if(isPalindrome(str) == true)
return str;
}
}
len--;
}
return "-1";
}
}
II.东哥的做法:
for 0 <= i < len(s):
找到以 s[i] 为中心的回文串
找到以 s[i] 和 s[i+1] 为中心的回文串
更新答案
class Solution {
public static String Palindrome(String s, int left, int right){
while(left >= 0 && right < s.length() && s.charAt(left) == s.charAt(right)){
// 双指针,向两边展开
left--;
right++;
}
return s.substring(left + 1, right);
}
public String longestPalindrome(String s) {
String res = "";
for(int i = 0; i < s.length(); i++){
//以 s[i] 为中心的最长回文子串
String str1 = Palindrome(s, i, i);
// 以 s[i] s[i+1] 为中心的最长回文子串
String str2 = Palindrome(s, i, i + 1);
res = res.length() < str1.length() ? str1 : res;
res = res.length() < str2.length() ? str2 : res;
}
return res;
}
}
- 先开始,我还以为代码有问题,就是Palindrome()这个函数的返回值为s.substring(left + 1, right);
- eg:“ 121 ”
- 以2为中心,扩大回文子串的时候,出了while循环,left < 0 && right == s.length,由于substring的特性,所以应该这么写,完全正确!
- “ 12 ”这个子串不是回文序列,所以left+1,返回的串就是 “ 2 ”
- “ 1 ”这个子串是回文序列,出了while,left < 0 越界了,left + 1,返回的子串就是“ 1 ”
2. 总结
- 东哥这个思路真有意思,回文子串就是从中心向两边进行发散,这个思路可以积累。
参考:https://labuladong.github.io/algo/di-yi-zhan-da78c/shou-ba-sh-48c1d/shuang-zhi-fa4bd/