用负索引和切片特性实现数组的旋转

  |  

摘要: 负索引和切片特性的应用:实现数组旋转

【对算法,数学,计算机感兴趣的同学,欢迎关注我哈,阅读更多原创文章】
我的网站:潮汐朝夕的生活实验室
我的公众号:算法题刷刷
我的知乎:潮汐朝夕
我的github:FennelDumplings
我的leetcode:FennelDumplings


本文我们以 leetcode 的一道题为例,看一下 Python 中如何利用负索引和切片特性实现 list 的旋转。


题目链接

1652. 拆炸弹

题目描述

你有一个炸弹需要拆除,时间紧迫!你的情报员会给你一个长度为 n 的 循环 数组 code 以及一个密钥 k 。

为了获得正确的密码,你需要替换掉每一个数字。所有数字会 同时 被替换。

如果 k > 0 ,将第 i 个数字用 接下来 k 个数字之和替换。
如果 k < 0 ,将第 i 个数字用 之前 k 个数字之和替换。
如果 k == 0 ,将第 i 个数字用 0 替换。
由于 code 是循环的, code[n-1] 下一个元素是 code[0] ,且 code[0] 前一个元素是 code[n-1] 。

给你 循环 数组 code 和整数密钥 k ,请你返回解密后的结果来拆除炸弹!

示例

示例 1:
输入:code = [5,7,1,4], k = 3
输出:[12,10,16,13]
解释:每个数字都被接下来 3 个数字之和替换。解密后的密码为 [7+1+4, 1+4+5, 4+5+7, 5+7+1]。注意到数组是循环连接的。

示例 2:
输入:code = [1,2,3,4], k = 0
输出:[0,0,0,0]
解释:当 k 为 0 时,所有数字都被 0 替换。

示例 3:
输入:code = [2,4,9,3], k = -2
输出:[12,5,6,13]
解释:解密后的密码为 [3+9, 2+3, 4+2, 9+4] 。注意到数组是循环连接的。如果 k 是负数,那么和为 之前 的数字。

提示

1
2
3
4
n == code.length
1 <= n <= 100
1 <= code[i] <= 100
-(n - 1) <= k <= n - 1

算法

首先我们预处理一个数组 sums,sums[i] 保存的是长度为 K(K = abs(k)) 的窗口 code[i + 1,…, i + K] 的和。

如果 k > 0,则 sums 直接就是答案。如果 k < 0,那么就要对 sums 做一次旋转。

我们分析 sums[0] 这个位置,这个位置的值是 code[1,…, K] 的和,而当 k < 0 的时候,该值应该保存在 sums[(K + 1) % n]。同理地,sums[1] 应该保存在 sums[(K + 2) % n],等等。

相当于最终的答案应该是对 sums 向右旋转 K + 1 次。也就是原数组 sums 的 [n-K-1..n-1] 部分放到 [0..n-K-2] 部分的前面,形成答案数组。

代码(C++)

C++ 中的 STL 中有一个 rotate 函数,实现数组的旋转。rotate(x.begin(), x.beign() + k, x.end()) 的含义是原数组 x 的 x.begin() + k 位置会成为新数组 x 的 x.begin() 位置。相当于数组左旋了 k 次。

而这里我们需要右旋 K + 1 次,还需要画个图算一下,中间的参数该填 n - (K + 1)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include <vector>
#include <cmath>
#include <algorithm>

using namespace std;

class Solution {
public:
vector<int> decrypt(vector<int>& code, int k) {
int n = code.size();
if(k == 0)
return vector<int>(n, 0);
vector<int> sums(n);
int K = abs(k);
for(int i = 0; i < K; ++i)
sums[0] += code[(i + 1) % n];
for(int i = 1; i < n; ++i)
sums[i] = sums[(i + n - 1) % n] - code[i] + code[(i + K) % n];
if(k > 0)
return sums;
else
{
int right_shift = -(k - 1);
rotate(sums.begin(), sums.begin() + (n - right_shift), sums.end());
return sums;
}
}
};

代码(Python)

在 Python 中,由于有负索引的特性,我们就不用像 C++ 代码里面那样算一下了。

原数组 sums 的 [n-K-1..n-1] 部分放到 [0..n-K-2] 部分的前面,直接可以写成 sums[:] = sums[-K-1] + sums[:-K-1],非常直观。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Solution:
def decrypt(self, code: List[int], k: int) -> List[int]:
n = len(code)
if k == 0:
return [0] * n
sums = [0] * n # sums[i] := [i + 1, ..., i + k] 的和
K = abs(k)
for i in range(K):
sums[0] += code[(i + 1) % n]
for i in range(1, n):
# 求 sums[i]
sums[i] = sums[(i + n - 1) % n] - code[i] + code[(i + K) % n]
if k > 0:
return sums
else:
right_shift = -(k - 1)
return sums[-right_shift:] + sums[:-right_shift]

Share