136. 只出现一次的数字
题干中要求做到线性时间复杂度和常数空间复杂度。
考虑使用位运算。使用异或运算有以下三个性质:
任何数和0 做异或运算,结果仍然是原来的数。
任何数和其自身做异或运算,结果是 0。
异或运算满足交换律和结合律。
class Solution {public int singleNumber(int[] nums) {int single = 0;for(int num : nums){single ^= num;}return single;}
}
169. 多数元素
可以使用哈希映射(HashMap)来存储每个元素以及出现的次数。对于哈希映射中的每个键值对,键表示一个元素,值表示该元素出现的次数。
在这之后,我们遍历哈希映射中的所有键值对,返回值最大的键。我们同样也可以在遍历数组 nums 时候使用打擂台的方法,维护最大的值,这样省去了最后对哈希映射的遍历。
class Solution {public int majorityElement(int[] nums) {// Arrays.sort(nums);// return nums[nums.length/2];// int votes =0;// int candidate=0;// for(int num :nums){// if(votes == 0){// candidate = num;// }// votes += (num == candidate) ? 1 : -1;// }// return candidate;int win=nums[0];int count=1;for(int i=1;i<nums.length;i++){if(nums[i] == win){count++;}else if(count == 0){win=nums[i];count=1;}else{count--;}}return win;}
}
75. 颜色分类
class Solution {public void sortColors(int[] nums) {int n = nums.length;// 分别表示0指针、2指针、当前索引int p0=0, p2=n-1, idx=0;// 待处理区域是(idx,r)// 0区域是(0,l)// 1区域是(l,idx)// 2区域是(r,n-1)while(idx<=p2){if(nums[idx]==0) swap(nums,p0++,idx++);else if(nums[idx]==1) idx++;// 注意: 当2移到后边,换回来的不是1时,指针要回退else swap(nums,p2--,idx);}}// 交换函数public static void swap(int[] nums,int l, int r){int temp = nums[l];nums[l]=nums[r];nums[r]=temp;}
}
31. 下一个排列
尽可能的将低位的数字变大,这样才符合「下一个排列」的定义。其实就是将 k
位到低位的所有数作为候选,判断是否有更大的数可以填入 k
位中。
换句话说,我们要找的第 k
位其实就是从低位到高位的第一个下降的数。
- 从后往前找,找到第一个下降的位置,记为 k。注意k 以后的位置是降序的。
- 在区间 [k+1,n)从后往前,找到最小的比 k 要大的数。
- 将两者交换。注意此时 k 以后的位置仍然是降序的。
- 利用双指针直接将 k 以后的部分翻转(变为升序)。
- 注意:如果在步骤 1 中找到头部还没找到,说明该序列已经是字典序最大的排列。跳过2、3,直接将数组翻转成最小的升序排列。
class Solution {public void nextPermutation(int[] nums) {int i = nums.length-2;// 1.找到较小的数Kwhile(i>=0 && nums[i]>=nums[i+1]){i--;}// 2.找到较大的数if(i>=0){int j=nums.length-1;while(j>=0 && nums[i]>=nums[j]){j--;}// 3.交换swap(nums,i,j);}// 4.翻转reverse(nums,i+1);}public void swap(int[] nums, int i, int j){int temp=nums[i];nums[i]=nums[j];nums[j]=temp;}public void reverse(int[] nums, int start){int left = start, right = nums.length-1;while(left<right){swap(nums,left,right);left++;right--;}}
}
287.寻找重复数.
1.二分查找法:
「数字都在 1到 n之间(包括 1和 n)」,查找一个有范围的整数,可以使用「二分查找」
- 「二分查找」的思路是先猜一个数(搜索范围 [left..right] 里位于中间的数 mid),然后统计原始数组中 小于等于 mid 的元素的个数 count:
- 如果 count 严格大于 mid。根据 抽屉原理,重复元素就在区间 [left..mid] 里;
- 否则,重复元素可以在区间 [mid + 1..right] 里找到。
2.快慢指针(如:142. 环形链表 II)
关键是要理解如何将输入的数组看作为链表。
首先明确前提,整数的数组 nums 中的数字范围是 [1,n]。
我们将数组下标 n 和数 nums[n] 建立一个映射关系为 n->f(n)。从下标为 0 出发,就可以产生一个类似链表一样的序列。
1.数组中有一个重复的整数 <==> 链表中存在环
2.找到数组中的重复整数 <==> 找到链表的环入口
class Solution {public int findDuplicate(int[] nums) {// 这里已经从起点0跳过一步了int slow=nums[0];int fast=nums[nums[0]];// 1.找到快慢指针相遇点while(slow!=fast){slow=nums[slow];fast=nums[nums[fast]];}// 2.找到入环口// 将快指针移回起点0,与慢指针同步移动fast=0;while(fast!=slow){fast=nums[fast];slow=nums[slow];}// 返回指针位置return fast;}
}
3.原地哈希(如:41. 缺失的第一个证书-)
class Solution {public int findDuplicate(int[] nums) {for(int i=0; i<nums.length;){int t=nums[i], idx=t-1;if(nums[idx]==t){if(idx!=i) return t;i++;}else{int temp = nums[i];nums[i]=nums[idx];nums[idx]=temp;}}return -1;}
}