核心思想
HOT100-技巧类型题
例题
1 只出现一次的数字
题目描述:
给你一个 非空 整数数组 nums ,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现了一次的元素。
你必须设计并实现线性时间复杂度的算法来解决此问题,且该算法只使用常量额外空间。
示例 1 :
**输入:**nums = [2,2,1]
**输出:**1
示例 2 :
**输入:**nums = [4,1,2,1,2]
**输出:**4
示例 3 :
**输入:**nums = [1]
**输出:**1
提示:
1 <= nums.length <= 3 * 104-3 * 104 <= nums[i] <= 3 * 104- 除了某个元素只出现一次以外,其余每个元素均出现两次。
解题思路:
- 使用性质:0n=n,nn=0,偶数次的利用结合律最后异或完都会为0,奇数次的最后异或结果为其本身
- 异或运算的结合律,交换律
代码如下
Java
class Solution {
public int singleNumber(int[] nums) {
int all=0;
for(int num:nums){
all^=num;
}
return all;
}
}
2、多数元素
题目描述:
给定一个大小为 n 的数组 nums ,返回其中的多数元素。多数元素是指在数组中出现次数 大于 ⌊ n/2 ⌋ 的元素。
你可以假设数组是非空的,并且给定的数组总是存在多数元素。
示例 1:
**输入:**nums = [3,2,3]
**输出:**3
示例 2:
**输入:**nums = [2,2,1,1,1,2,2]
**输出:**2
提示:
n == nums.length1 <= n <= 5 * 104-109 <= nums[i] <= 109- 输入保证数组中一定有一个多数元素。
**进阶:**尝试设计时间复杂度为 O(n)、空间复杂度为 O(1) 的算法解决此问题。
解题思路:
- 把相同元素当作同一个阵营的人,人数等于阵营血量
- 那么次数大于大于
⌊ n/2 ⌋的元素是其中血量最厚的那个 - 阵营间大战,不同阵营一换一
- 阵营之间作战,最后还剩下的血量不为0的就是寻找的阵营
代码如下
Java
class Solution {
public int majorityElement(int[] nums) {
//记录最终答案
int ans=0;
//记录答案出现次数,即ans的血量
int hp=0;
for(int num:nums){
if(hp==0){
ans=num;
hp=1;
}else{
//若双方同阵营,则加血
hp+=num==ans?1:-1;
}
}
return ans;
}
}
3、 颜色分类
题目描述:
给定一个包含红色、白色和蓝色、共 n 个元素的数组 nums ,原地 对它们进行排序,使得相同颜色的元素相邻,并按照红色、白色、蓝色顺序排列。
我们使用整数 0、 1 和 2 分别表示红色、白色和蓝色。
必须在不使用库内置的 sort 函数的情况下解决这个问题。
示例 1:
**输入:**nums = [2,0,2,1,1,0]
输出:[0,0,1,1,2,2]
示例 2:
**输入:**nums = [2,0,1]
输出:[0,1,2]
提示:
n == nums.length1 <= n <= 300nums[i]为0、1或2
进阶:
- 你能想出一个仅使用常数空间的一趟扫描算法吗?
解题思路:
- 首先这个题的数据只有0,1,2
- 需要以O (N) 的时间复杂度,O (1) 的空间复杂度完成
- 步骤如下
- 步骤1 :初始化
p0和p1分别指向下一个放 0 和 1 的位置,原因是用它们动态划分出 0 区和 1 区的边界。 - 步骤2 :从左到右遍历每个位置
i取出原值num,原因是单趟扫描要求每个元素只处理一次。 - 步骤3 :先把
nums[i]直接设为 2,原因是把当前位置先当作默认的 2 区并清空旧值避免干扰。 - 步骤4 :若
num<=1就在p1处写 1 并p1++,原因是 0 或 1 都会让 1 区向右扩展一格(0 先占位)。 - 步骤5 :若
num==0再在p0处写 0 并p0++,原因是把真正的 0 放进最前面的 0 区并覆盖刚才的占位 1。
- 步骤1 :初始化
代码如下
Java
class Solution {
public void sortColors(int[] nums) {
//下一个应该放置0/1的位置
int p0=0;
int p1=0;
for(int i=0,num;i<nums.length;i++){
num=nums[i];
//先让nums[i]被2覆盖
nums[i]=2;
//再让nums[i]为1
if(num<=1){
nums[p1++]=1;
}
//让nums[i]
if(num==0){
nums[p0++]=0;
}
}
}
}
4、下一个排列
题目描述:
整数数组的一个 排列 就是将其所有成员以序列或线性顺序排列。
- 例如,
arr = [1,2,3],以下这些都可以视作arr的排列:[1,2,3]、[1,3,2]、[3,1,2]、[2,3,1]。
整数数组的 下一个排列 是指其整数的下一个字典序更大的排列。更正式地,如果数组的所有排列根据其字典顺序从小到大排列在一个容器中,那么数组的 下一个排列 就是在这个有序容器中排在它后面的那个排列。如果不存在下一个更大的排列,那么这个数组必须重排为字典序最小的排列(即,其元素按升序排列)。
- 例如,
arr = [1,2,3]的下一个排列是[1,3,2]。 - 类似地,
arr = [2,3,1]的下一个排列是[3,1,2]。 - 而
arr = [3,2,1]的下一个排列是[1,2,3],因为[3,2,1]不存在一个字典序更大的排列。
给你一个整数数组 nums ,找出 nums 的下一个排列。
必须 原地 修改,只允许使用额外常数空间。
示例 1:
**输入:**nums = [1,2,3]
输出:[1,3,2]
示例 2:
**输入:**nums = [3,2,1]
输出:[1,2,3]
示例 3:
**输入:**nums = [1,1,5]
输出:[1,5,1]
提示:
1 <= nums.length <= 1000 <= nums[i] <= 100
解题思路:
- 从右向左,找第一个小于右侧相邻数字的数 x
- 找 x 右边最小的大于 x 的数 y,交换 x 和 y
- 反转 y 右边的数,把右边的数变成最小的排列
代码如下
Java
class Solution {
public void nextPermutation(int[] nums) {
int n = nums.length;
//寻找数组中最后一个有上升趋势的位置,例如num[i]<num[i+1],这就是一个上升趋势,pos取i值
int pos = n - 2;
while (pos >= 0 && nums[pos] >= nums[pos + 1]) {
pos--;
}
//说明找到了最后的一个上升趋势
if (pos >= 0) {
//找到pos右侧大于pos指向的数的最小的那个
//由于pos是最后一个上升趋势,因此pos+1往后的数一定是一个递减的序列
int j = n - 1;
while (nums[j] <= nums[pos]) {
j--;
}
swap(nums, pos, j);
//这样交换后,由于j是大于pos的最小的数,所以交换后pos+1往后仍然是一个非递增序列
}
//现在交换完后,当前字典序已经大于之前的了,但是我们是要找下一个,也就是以前缀[0....pos]后缀[pos+1...n-1]最小的那个
//即pos+1是一个非递增,将其逆序,变为非递减
reverse(nums, pos + 1, n - 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 i, int j) {
while (i <= j) {
int temp = nums[i];
nums[i] = nums[j];
nums[j] = temp;
i++;
j--;
}
}
}
5、寻找重复数
题目描述:
给定一个包含 n + 1 个整数的数组 nums ,其数字都在 [1, n] 范围内(包括 1 和 n),可知至少存在一个重复的整数。
假设 nums 只有 一个重复的整数 ,返回 这个重复的数 。
你设计的解决方案必须 不修改 数组 nums 且只用常量级 O(1) 的额外空间。
示例 1:
**输入:**nums = [1,3,4,2,2]
**输出:**2
示例 2:
**输入:**nums = [3,1,3,4,2]
**输出:**3
示例 3 :
**输入:**nums = [3,3,3,3,3]
**输出:**3
提示:
1 <= n <= 105nums.length == n + 11 <= nums[i] <= nnums中 只有一个整数 出现 两次或多次 ,其余整数均只出现 一次
进阶:
- 如何证明
nums中至少存在一个重复的数字? - 你可以设计一个线性级时间复杂度
O(n)的解决方案吗?
解题思路:
- 这个题可以转化为一个环形链表模型,每个数字就变成了链表中的节点
- 重复数字相当于环形链表环形入口,重复数值入度为2,其余节点入度为1
- 转为环形链表模型后,解法其实和环形链表Ⅱ一样
- 只不过这里由于给定结构是数组,那么链表的next指针就变为了nums[节点值]
- 使用快慢双指针
代码如下
Java
class Solution {
public int findDuplicate(int[] nums) {
int n=nums.length;
if(nums==null || n<2){
return -1;
}
//快慢双指针
int slow=nums[0];
int fast=nums[nums[0]];
//快指针每次走2步,慢指针每次走一步
while(slow!=fast){
slow=nums[slow];
fast=nums[nums[fast]];
}
//相同后,快指针从头出发,每次只走一步
fast=0;
while(slow!=fast){
fast=nums[fast];
slow=nums[slow];
}
return slow;
}
}