学习目标:
本次学习目标为 力扣初级算法-数组,其中主要的LC如下:
- 旋转数组
- 存在重复元素
学习内容:
- 旋转数组 (链接)
给定一个数组,将数组中的元素向右移动 k 个位置,其中 k 是非负数。
进阶:
尽可能想出更多的解决方案,至少有三种不同的方法可以解决这个问题。
你可以使用空间复杂度为 O(1) 的 原地 算法解决这个问题吗?示例1:
输入: nums = [1,2,3,4,5,6,7], k = 3
输出: [5,6,7,1,2,3,4]
解释:
向右旋转 1 步: [7,1,2,3,4,5,6]
向右旋转 2 步: [6,7,1,2,3,4,5]
向右旋转 3 步: [5,6,7,1,2,3,4]示例2:
输入:nums = [-1,-100,3,99], k = 2
输出:[3,99,-1,-100]
解释:
向右旋转 1 步: [99,-1,-100,3]
向右旋转 2 步: [3,99,-1,-100]
解题思路:
-
解法一: 临时数组
- 边界问题:数组为null 和数组大小为0的情况,直接return;
- 代码逻辑过程:
- 开辟一个新数组,将老数组的值复制到新数组中
- 是否存在边界问题:否
- 遍历老数组,从index == 0 开始循环,其中以新数组为参照,将 index + 偏移量的值重写到老数组中,完成替换。
- 代码实现:
public class Rotate { // 解法一: 使用临时数组 public void rotate(int[] nums, int k) { int length = nums.length; int[] temp = new int[length]; // 备份原数组 System.arraycopy(nums, 0, temp, 0, length); // 转移偏移后的数据到原数组 -- 假设长度为 6 移动 3, 0+3/6 = 3 for (int i =0; i< length; i++){ int offsetIndex = (i + k ) % length; nums[offsetIndex] = temp[i]; } } }
-
解法二:多次反转
- 首先将数组内的所有元素反转,再反转前K个,最后反转剩下的几个
- 是否存在边界问题:否
- 注意点
- 二次反转可保证原先的排序
- 代码实现
/** * 解法二 多次反转 */ public void rotate02(int[] nums, int k) { int length = nums.length; k %= length; //先反转前面的 reverse(nums, 0, length - k - 1); //接着反转后面k个 reverse(nums, length - k, length - 1); //最后在反转全部的元素 reverse(nums, 0, length - 1); } /** * 反转数组中的元素 */ public void reverse(int[] nums, int start, int end){ if (nums.length != 0 ){ int temp; while (start < end){ temp = nums[start]; nums[start++] = nums[end]; nums[end--] = temp; } } }
-
解法三:环形旋转
- 解法类似约瑟夫环,把数组当作是环形,每一个都往后移动K位
- 是否存在边界问题:是,考虑数组大小为偏移量整数倍的情况,即 nums.length%k=0,针对边界问题,为防止循环打转,可新增一个数组用来标识当前元素有没有被访问过,当已访问过时,则从下个元素继续开始。
- 代码逻辑过程:
- 声明单个临时变量 hold,用来存放当前取出的元素。
- 声明与入参同大小的数组 existArray ,用来存放当前偏移量后的坐标是否已被操作过。
- 循环遍历入参数组
- 将当前坐标加上偏移量除于数组大小,得出的结果为 偏移后的坐标(此处应该注意偏移量整数倍的情况)— index = (index + k) % length;
- 判断上述的偏移后的坐标 index 是否已存在于 existArray 。
- 当 index 不存在于 existArray 时,先将当前 index 标记为已被操作 (existArray [index] = true),其次将即将被移动的 index 原有元素放入 temp 中,再将 hold 写入偏移后的 index 位,最后再将被替换的元素放入 hold中。
- 当 index 存在于 existArray,将 index = index + 1, 先跳出当前的循环操作,其次计算当前 index 后的偏移量,再将下次需要便宜的元素放入 hold 中,同时减掉全量的循环次数,避免重复循环。
- 判断完再继续for循环。
- 代码实现
/**
* 解法三 多次反转
*/
public void rotate03(int[] nums, int k) {
int hold =nums[0];
int index = 0;
int length = nums.length;
boolean[] existArray = new boolean[length];
for (int i = 0; i < length; i++) {
index = (index + k) % length;
if (existArray[index]) {
//避免现原地打转
index = (index + 1) % length;
hold = nums[index];
i--;
} else {
existArray[index] = true;
int temp = nums[index];
nums[index] = hold;
hold = temp;
}
}
}
学习笔记:
- 什么是 约瑟夫环?
约瑟夫环(约瑟夫问题)是一个数学的应用问题:已知 n 个人(以编号1,2,3…n分别表示)围坐在一张圆桌周围。从编号为 k 的人开始报数,数到 m 的那个人出圈;他的下一个人又从 1 开始报数,数到 m 的那个人又出圈;依此规律重复下去,直到剩余最后一个胜利者。
学习总结:
- 多加边界问题
- 循环数组应该考虑重复操作问题