目录
题目链接:189. 轮转数组 - 力扣(LeetCode)
题目描述
示例
提示:
知识补充ArrayDeque ()
ArrayDeque 的特点:
常用方法:
详细示例:
运行结果:
总结:
解法一:使用队列模拟
Java写法:
运行时间
C++写法:
运行时间
编辑
解法二:三次翻转数组+双指针
Java写法:
运行时间
C++写法
运行时间
总结
题目链接:189. 轮转数组 - 力扣(LeetCode)
注:下述题目描述和示例均来自力扣
题目描述
给定一个整数数组 nums
,将数组中的元素向右轮转 k
个位置,其中 k
是非负数。
示例
示例 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]
提示:
1 <= nums.length <=
- <= nums[i] <= - 1
0 <= k <=
知识补充ArrayDeque
()
ArrayDeque
是 Java 提供的一个基于数组实现的双端队列(deque),全名是 "Array Double Ended Queue"。它是 Deque
接口的实现之一,既可以用作队列(FIFO,先进先出),也可以用作栈(LIFO,后进先出)。它底层基于循环数组实现,具有较高的效率,可以在队列两端进行快速的插入和删除操作。
ArrayDeque
的特点:
-
双端操作:
ArrayDeque
允许我们在队列的头部和尾部同时进行元素的添加和删除操作,这使得它既能模拟队列,又能模拟栈。- 由于它是一个双端队列,因此可以灵活使用栈和队列的典型操作。
-
动态数组扩展:
- 底层是基于循环数组实现的。当数组满了时,
ArrayDeque
会自动扩容(通常会加倍数组大小),因此无需担心容量问题。 - 相比于
LinkedList
,ArrayDeque
的扩容代价相对较低,因为它直接在数组中操作数据,而无需额外的节点对象和指针。
- 底层是基于循环数组实现的。当数组满了时,
-
高效操作:
- 插入和删除操作的时间复杂度都是 O(1),不涉及复杂的指针操作,因此效率非常高。
- 不建议使用
ArrayDeque
来做随机访问(比如通过索引访问某个元素),因为这是线性时间操作 O(n),适合的场景是作为队列或栈使用。
-
线程不安全:
ArrayDeque
不是线程安全的,如果在多线程环境中使用,需要自行实现同步机制,比如通过synchronized
块或使用并发队列如ConcurrentLinkedDeque
。
常用方法:
-
添加元素:
offer(E e)
:将元素添加到队列的尾部(队列特性)。offerFirst(E e)
:将元素添加到队列的头部(双端队列特性)。offerLast(E e)
:将元素添加到队列的尾部,和offer
相同。
-
移除元素:
poll()
:移除并返回队列头部的元素(队列特性),如果队列为空,返回null
。pollFirst()
:移除并返回队列头部的元素(双端队列特性)。pollLast()
:移除并返回队列尾部的元素。
-
获取元素:
peek()
:返回队列头部的元素但不移除它(队列特性),如果队列为空,返回null
。peekFirst()
:返回队列头部的元素但不移除它(双端队列特性)。peekLast()
:返回队列尾部的元素但不移除它。
-
栈操作:
push(E e)
:将元素压入栈顶(相当于addFirst
操作,栈特性)。pop()
:移除并返回栈顶元素(相当于pollFirst
操作,栈特性)。
详细示例:
import java.util.ArrayDeque;
public class ArrayDequeExample {
public static void main(String[] args) {
// 创建一个ArrayDeque
ArrayDeque<Integer> deque = new ArrayDeque<>();
// 队列操作
deque.offer(10); // 添加到尾部
deque.offer(20); // 添加到尾部
deque.offer(30); // 添加到尾部
System.out.println("队列内容: " + deque);
// 出队操作(FIFO,先进先出)
int firstElement = deque.poll(); // 移除并返回头部元素
System.out.println("出队元素: " + firstElement);
System.out.println("队列剩余内容: " + deque);
// 栈操作
deque.push(40); // 将元素压入栈顶
deque.push(50); // 将元素压入栈顶
System.out.println("栈内容: " + deque);
// 出栈操作(LIFO,后进先出)
int topElement = deque.pop(); // 移除并返回栈顶元素
System.out.println("出栈元素: " + topElement);
System.out.println("栈剩余内容: " + deque);
}
}
运行结果:
队列内容: [10, 20, 30]
出队元素: 10
队列剩余内容: [20, 30]
栈内容: [50, 40, 20, 30]
出栈元素: 50
栈剩余内容: [40, 20, 30]
总结:
ArrayDeque
非常适合作为队列和栈使用,因为它提供了高效的双端插入、删除操作。ArrayDeque
相较于LinkedList
来说在大多数场景下更高效,尤其是在不涉及频繁的随机访问时。- 它是
Deque
接口的常用实现,灵活且高效。
解法一:使用队列模拟
Java写法:
class Solution {
public void rotate(int[] nums, int k) {
/**
1,2,3,4,5,6,7
向右轮转 1 步: [7,1,2,3,4,5,6]
向右轮转 2 步: [6,7,1,2,3,4,5]
向右轮转 3 步: [5,6,7,1,2,3,4]
*/
// 创建一个队列准备模拟这个过程
ArrayDeque<Integer> deque = new ArrayDeque<>();
// 将数组中的值都添加进入队列中
for (int num : nums) {
deque.offer(num);
}
// 轮转K次
for (int i = 1; i <= k; i++) {
Integer last = deque.pollLast();
deque.offerFirst(last);
}
// 再重新更新数组中的值
int i = 0;
for (Integer integer : deque) {
nums[i] = integer;
i++;
}
}
}
运行时间
C++写法:
class Solution {
public:
void rotate(vector<int>& nums, int k) {
// 如果k大于数组长度,则取模简化问题
k %= nums.size();
if (k == 0) return; // 如果k为0,则无需旋转
// 创建一个deque准备模拟这个过程
std::deque<int> deque(nums.begin(), nums.end());
// 轮转K次
for (int i = 0; i < k; i++) {
int last = deque.back(); // 取出最后一个元素
deque.pop_back(); // 从deque尾部删除元素
deque.push_front(last); // 将取出的元素插入到deque的头部
}
// 再重新更新数组中的值
int i = 0;
for (int num : deque) {
nums[i++] = num;
}
}
};
运行时间
挺慢的但你就说过没过吧
解法二:三次翻转数组+双指针
Java写法:
class Solution {
public void rotate(int[] nums, int k) {
/**
1,2,3,4,5,6,7
7,6,5,4,3,2,1
分别翻转
5,6,7, 1,2,3,4
*/
int len = nums.length;
// 如果k>len了那么只需要取余,因为轮转len个位置等于没有轮转
if(k > len){
k %= len;
}
// 先整体的翻转一下
reverseArray(nums,0,len - 1);
// 翻转前k位
reverseArray(nums,0,k - 1);
// 翻转剩下的
reverseArray(nums,k,len - 1);
}
/**
* 翻转数组的指定位置
* @param arr 目标数组
* @param start 翻转起始索引
* @param end 翻转结束索引
*/
public void reverseArray(int[] arr,int start,int end){
// 利用前后两个指针来交换(也就是翻转数组)
while (start < end){
int temp = arr[start];
arr[start] = arr[end];
arr[end] = temp;
// 移动指针
start++;
end--;
}
}
}
运行时间
C++写法
class Solution {
public:
void rotate(vector<int>& nums, int k) {
int len = nums.size();
// 如果k>len了那么只需要取余,因为轮转len个位置等于没有轮转
k = k % len;
// 先整体的翻转一下
reverseArray(nums, 0, len - 1);
// 翻转前k位
reverseArray(nums, 0, k - 1);
// 翻转剩下的
reverseArray(nums, k, len - 1);
}
/**
* 翻转数组的指定位置
* @param arr 目标数组
* @param start 翻转起始索引
* @param end 翻转结束索引
*/
void reverseArray(std::vector<int>& arr, int start, int end) {
// 利用前后两个指针来交换(也就是翻转数组)
while (start < end) {
std::swap(arr[start], arr[end]);
// 移动指针
start++;
end--;
}
}
};
运行时间
总结
其实我觉得直接使用队列还是更好理解更加的简单,虽然优化只是一个翻转数组但是也是极其的简单的操作。