前言
- 只用Java。
136.只出现一次的数字
关键信息一句话总结:
利用异或的性质:a ^ a = 0,a ^ 0 = a,所有元素异或后剩下的就是只出现一次的数。
- 难点1:只有一个数字出现了一次,怎么排除掉其他数字?
- 分析1:可能是老老实实的计数,但是有邪门的方法。异或运算。a ^ 0 = a和a ^ a = 0。
java
class Solution {
public int singleNumber(int[] nums) {
int total = 0;
// 异或运算 a ^ 0 = a和a ^ a = 0
// 那么 a ^ a ^ b => 0 ^ b = b
for(int i = 0; i < nums.length; i++) {
total ^= nums[i];
}
return total;
}
}
- 反思:我在洛谷做过这道题所以有点印象。我没有想到异或运算的性质,下一次遇到类似的数字的配对问题,可以考虑一下异或运算。
169.多数元素
关键信息一句话总结:
维护一个候选人 cand 和票数 cnt,相同就 cnt++,不同就 cnt--,cnt==0 时换候选人;最后候选人就是多数元素。摩尔投票。
- 难点1:没有想到空间为O(1)的做法
- 分析1:普通做法是用一个计数数组或者哈希表,但是空间O(n)。这一题是求众数,众数有个特点,就是个数大于其他数,那么众数出现的次数-遇到其他数的次数 > 0,好,那么就可以实现所谓的
摩尔投票了
java
class Solution {
public int majorityElement(int[] nums) {
// 摩尔投票 算法原理:众数的数量大于其他数的数量
int count = 0;
int candidate = nums[0];
for(int i = 0; i < nums.length; i++) {
if(count == 0) {
candidate = nums[i];
}
if(nums[i] == candidate) {
count ++;
}
if(nums[i] != candidate){
count --;
}
}
return candidate;
}
}
- 反思:我没有想到这种空间为O(1)的做法,我下次应该多考虑这种数的性质,就是要从性质和事实入手,用程序实现出这种思想,算法要注重原理,不是漫无目的的寻找,多观察事实的性质。
75.颜色分类
关键信息一句话总结:
三指针分区交换值
- 方法1:三指针分区交换值,荷兰国旗问题
java
class Solution {
public void sortColors(int[] nums) {
// 荷兰国旗问题 三指针分区 思想
int left = 0;
int right = nums.length - 1;
int i = 0;
while(i <= right) {
if(nums[i] == 0) {
swap(nums, i, left);
left++;
i++;
} else if(nums[i] == 1) {
i++;
} else {
swap(nums, i, right);
right--;
}
}
}
private void swap(int[] nums, int a, int b) {
int temp = nums[a];
nums[a] = nums[b];
nums[b] = temp;
}
}
- 方法2:计数思想
java
class Solution {
public void sortColors(int[] nums) {
int red = 0;
int white = 0;
int blue = 0;
for(int i = 0; i < nums.length; i++) {
if(nums[i] == 0) {
red++;
}
if(nums[i] == 1) {
white++;
}
if(nums[i] == 2) {
blue++;
}
}
int current = 0;
while(red > 0) {
nums[current] = 0;
red--;
current++;
}
while(white > 0) {
nums[current] = 1;
white--;
current++;
}
while(blue > 0) {
nums[current] = 2;
blue--;
current++;
}
}
}
- 反思:我没有想到三指针分区,这道题想告诉我的是分区思想
31.下一个排列
关键信息一句话总结:
找到下降点,从后往前找到并交换第一个比他大的数,再将后半部分逆置
java
class Solution {
public void nextPermutation(int[] nums) {
// 1.从右往左找第一个下降点 右边都是递增的
int i = nums.length - 2;
while(i >= 0 && nums[i] >= nums[i + 1]) {
i--;
}
// 2.从右往左找第一个比 nums[i] 大的数 最小改变 交换第一个比它大的数
if(i >= 0) {
int j = nums.length - 1;
while(nums[j] <= nums[i]) {
j--;
}
swap(nums, i, j);
}
// 3.交换后,把 i+1 到结尾反转 因为反转一次保证字典序最小
reverse(nums, i + 1);
}
private void swap(int[] nums, int a, int b) {
int temp = nums[a];
nums[a] = nums[b];
nums[b] = temp;
}
private void reverse(int[] nums, int start) {
int left = start;
int right = nums.length - 1;
while(left < right) {
swap(nums, left, right);
left++;
right--;
}
}
}
- 反思:我没有想到保证字典序的含义,要保证最小的操作,这道题想告诉我的是如何维护一个字典序,我没有想明白为什么最后要倒置,其实就是为了保证最小变动,字典序只能大一阶,所以要倒置
287.寻找重复数
关键信息一句话总结:
抽象为用Floyid环算法寻找环的入口
- 分析:这与我们之前的链表的判断环有何区别,这里的数组的value指向的是下一个节点的index,也就是 slow = nums[slow] 对应 slow = slow.next,fast = nums[nums[fast]] 对应 fast = fast.next.next
java
class Solution {
public int findDuplicate(int[] nums) {
// 抽象成寻找环的入口
// 数组的value指向下一节点的index
int slow = nums[0];
int fast = nums[0];
// 1.找到相遇点
while(true) {
slow = nums[slow];
fast = nums[nums[fast]];
if(slow == fast) {
break;
}
}
// 2.找入口
int head = nums[0];
while(slow != head) {
head = nums[head];
slow = nums[slow];
}
return head;
}
}
- 反思:我没有想到能够抽象成一个环