原地旋转字符串

public class Rotation {

	
	public static void rotate(char[] s, int last, int left) throws Exception{
		if(s == null){
			throw new Exception("");
		}
		if(s.length == 0){
			throw new Exception("");
		}
		int right = s.length - 1;
		if(left == right){
			return ;
		}
		if(last<left || last>right){
			return;
		}
		int middle = (left + right)/2;
		if(last < middle){
			for(int i=0; i<=(last-left); i++){
				swap(s, i, left, last);
			}
			int newLast = 2*last -left +1;
			left = last + 1;
			rotate(s, newLast, left);
		}else{
			for(int j=0; j<right-last; j++){
				swap(s, j, left, last);
			}
			left = left + right -last;
			rotate(s, last, left);
		}
	}
	
	private static void swap(char[] s, int k, int left, int last){
		char temp = s[left+k];
		s[left+k] = s[last+1+k];
		s[last+1+k] = temp;
	}
	/**
	 * @param args
	 */
	public static void main(String[] args) {
		String s = "abcdefghijklmn";
		char[] ss = s.toCharArray();
		try {
			Rotation.rotate(ss, 6, 0);
			System.out.println(new String(ss));
		} catch (Exception e) {
			e.printStackTrace();
		}

	}

}

<think>我们讨论字符串位移操作,特别是左旋转操作。根据引用[1]和引用[2],我们可以总结几种方法。 题目要求:将字符串前面的若干个字符转移到字符串尾部。例如,输入"abcdefg"和数字2,返回"cdefgab"。 方法1:暴力遍历法(模拟法) 思路:将前k个字符逐个取出,然后拼接到剩余字符串的末尾。 步骤: 1. 取出前k个字符 2. 将剩余字符串(从第k+1个字符到末尾)与取出的前k个字符拼接 方法2:空间换时间法(切片法) 思路:利用字符串切片操作,直接截取子串并拼接。 步骤: 1. 将字符串分为两部分:前k个字符和剩余字符 2. 将两部分交换顺序拼接 方法3:库函数复制法(使用字符串复制函数) 思路:利用字符串复制函数(如Python的切片操作)实现,与方法2类似。 方法4:取余法(循环移位) 思路:将字符串看作一个环,每个字符向左移动k位,实际上就是每个字符的新位置为 (i - k) mod n,其中n为字符串长度。 但是,这种方法需要额外的空间存储新字符串。 方法5:求逆交换法(三次翻转法) 思路:通过三次翻转实现: 1. 翻转前k个字符 2. 翻转剩余的字符 3. 翻转整个字符串 例如:s = "abcdefg", k=2 1. 翻转前2个:得到"bacdefg" 2. 翻转剩余部分:得到"bagfedc" 3. 翻转整个字符串:得到"cdefgab" 注意:在编程中,我们通常使用索引操作实现翻转。 下面我们用代码展示几种主要方法(以Python为例): 方法2(切片法)代码: ```python def left_rotate_string(s, k): if not s or k == 0: return s n = len(s) k = k % n # 避免k大于n return s[k:] + s[:k] ``` 方法5(三次翻转)代码: ```python def left_rotate_string(s, k): if not s or k == 0: return s n = len(s) k = k % n # 将字符串转换为列表,因为Python中字符串不可变 s_list = list(s) # 翻转前k个字符 s_list[0:k] = reversed(s_list[0:k]) # 翻转剩余字符 s_list[k:] = reversed(s_list[k:]) # 翻转整个字符串 s_list.reverse() return ''.join(s_list) ``` 但是,注意三次翻转法的经典步骤是: 1. 翻转前k个字符 2. 翻转后n-k个字符 3. 翻转整个字符串 这样得到的结果是左旋转。但上面的代码是先翻转前k个,再翻转后n-k个,然后整体翻转。实际上,我们也可以先整体翻转,然后再分别翻转两个部分?其实经典步骤就是上述三步。 另一种经典写法(更符合数学推导): 设字符串为s,长度为n,要左旋转k位。 步骤: s1 = reverse(s[0:k]) # 前k个翻转 s2 = reverse(s[k:n]) # 剩余部分翻转 s3 = s1 + s2 result = reverse(s3) # 整体翻转 但是注意,整体翻转后,实际上得到的是原字符串旋转k位的结果。 但是,我们也可以这样: 先整体翻转:s' = reverse(s) -> "gfedcba" 然后翻转前n-k个:reverse(s'[0:n-k]) -> 翻转前5个(n=7, k=2): 翻转"gfedc" -> "cdefg" 再翻转后k个:reverse(s'[n-k:]) -> 翻转"ba" -> "ab" 然后拼接: "cdefg" + "ab" = "cdefgab" 所以,另一种三次翻转: def left_rotate_string(s, k): n = len(s) k = k % n s_list = list(s) # 整体翻转 s_list.reverse() # 翻转前n-k个 s_list[0:n-k] = reversed(s_list[0:n-k]) # 翻转后k个 s_list[n-k:] = reversed(s_list[n-k:]) return ''.join(s_list) 但是,这种方法需要两次局部翻转,而经典方法也是三次翻转,但经典方法在描述上通常是:先翻转前k个,再翻转后n-k个,再整体翻转。实际上,这两种方式都是可行的,但要注意顺序。 实际上,经典三次翻转(原地): 步骤1:翻转前k个字符 步骤2:翻转剩余字符(从k+1到末尾) 步骤3:翻转整个字符串 为什么这样就能得到左旋转? 设原字符串为AB,其中A为前k个,B为剩余部分,我们要得到BA。 步骤1:翻转A -> A'(A的逆序) 步骤2:翻转B -> B'(B的逆序) 步骤3:翻转整个A'B' -> (A'B')' = (B')' (A')' = BA 所以,经典三步: s = A + B step1: A' + B step2: A' + B' step3: (A' + B')' = BA 因此,我们按照经典步骤写: 方法5(经典三次翻转): ```python def left_rotate_string(s, k): if not s or k == 0: return s n = len(s) k = k % n s_list = list(s) # 翻转前k个 s_list[0:k] = s_list[0:k][::-1] # 或者用reversed,但注意reversed返回的是迭代器 # 翻转剩余部分 s_list[k:] = s_list[k:][::-1] # 翻转整个字符串 s_list.reverse() return ''.join(s_list) ``` 注意:我们也可以不用每次都转换成列表再翻转,但字符串不可变,所以需要列表操作。 另外,引用[1]中提到的取余法,实际上就是循环移位,但需要额外空间,我们可以这样: new_s = [] n = len(s) for i in range(n): new_s.append(s[(i+k) % n]) # 这样是循环左移k位?不对,这样是左移后,每个字符的位置都向后移动了k位,实际上是右移?或者左移? 实际上,循环左移k位:原来位置i的字符应该移动到 (i-k) mod n 的位置,也就是新位置为 (i-k+n) mod n。 但是,我们也可以这样:新字符串的第i个字符等于原字符串的第 (i+k) mod n 个字符。这样得到的是循环右移k位。 所以,循环左移k位:新字符串的第i个字符等于原字符串的第 (i+k) mod n 个字符?不对。 正确理解: 原字符串:s[0], s[1], ..., s[n-1] 循环左移k位:从s[k]开始,然后s[k+1]...s[n-1],然后s[0]...s[k-1] 所以,新字符串的第0个字符是s[k],第1个是s[k+1],...,第n-k-1个是s[n-1],第n-k个是s[0],...,第n-1个是s[k-1]。 因此,新字符串的第i个字符 = s[(i+k) % n] ??? 不对,因为当i=0时,应该是s[k],而(k)%n=k,所以是s[k];当i=n-k时,应该是s[0],而(n-k+k)%n = n%n=0,所以是s[0];当i=n-k-1时,应该是s[n-1],而(n-k-1+k)%n = (n-1)%n = n-1,正确。 所以,实际上循环左移k位后,新字符串的第i个字符等于原字符串的第 (i+k) mod n 个字符。但是,这实际上是循环左移?还是循环右移? 注意:循环左移k位,相当于循环右移n-k位。所以,如果我们用新字符串的第i个位置取原字符串的第(i+k) mod n,实际上是循环左移k位?不对,因为: 循环左移k位:原字符串的每个字符都向左移动了k位,那么原来位置在k的字符移动到了0位置,位置在k+1的移动到1位置,...,位置在0的字符移动到了n-k位置,位置在k-1的字符移动到了n-1位置。 所以,新字符串的第0位置是原字符串的第k位置,第1位置是原字符串的第k+1位置,...,第n-k-1位置是原字符串的第n-1位置,第n-k位置是原字符串的第0位置,...,第n-1位置是原字符串的第k-1位置。 因此,新字符串的第i位置 = 原字符串的第 (i+k) mod n 位置?不对,因为当i=0时,新位置0对应原位置k,即原位置k,而(i+k) mod n = k mod n = k,正确。 当i=n-k时,新位置n-k对应原位置0,而(i+k) mod n = (n-k+k) mod n = n mod n = 0,正确。 所以,循环左移k位后,新字符串的第i个字符等于原字符串的第 (i+k) mod n 个字符。但是,这里我们通常使用取余法来构建新字符串,需要额外空间。 取余法代码: ```python def left_rotate_string(s, k): n = len(s) if n == 0 or k == 0: return s k = k % n res = [] for i in range(n): # 新字符串的第i个位置对应原字符串的 (i+k) % n 位置 # 但是注意:循环左移k位后,原字符串中位置j的字符会出现在新字符串的 (j-k) mod n 位置。 # 而我们这里直接构建新字符串:新位置i的字符来自原字符串的位置 (i+k) mod n? # 实际上,我们想要的是:新位置0 -> 原位置k,新位置1->原位置k+1,...,新位置n-k-1->原位置n-1,新位置n-k->原位置0,...,新位置n-1->原位置k-1 # 所以,新位置i的字符 = 原字符串位置 (i+k) mod n 的字符?不对,因为当i=0时,我们取原字符串位置k,正确;当i=n-k时,取原字符串位置 (n-k+k) mod n = n mod n =0,正确。 # 所以,确实可以这样: res.append(s[(i+k) % n]) return ''.join(res) ``` 但是,运行一下例子:s="abcdefg", k=2 新字符串: i=0: (0+2)%7=2 -> s[2]='c' i=1: 3 -> 'd' i=2: 4 -> 'e' i=3: 5 -> 'f' i=4: 6 -> 'g' i=5: (5+2)%7=7%7=0 -> 'a' i=6: (6+2)%7=8%7=1 -> 'b' 所以新字符串是"cdefgab",正确。 但是,注意:这样实际上是循环右移了n-k位?因为循环右移k位:新位置i的字符来自原字符串位置(i-k) mod n,而这里我们取的是(i+k) mod n,相当于循环左移k位?不对,循环左移k位通常的定义是将字符串的前k个字符移到后面,而循环右移k位是将字符串的后k个字符移到前面。所以这里我们实现的是循环左移k位。 因此,取余法可以这样实现。 总结: 1. 切片法(最简单,推荐) 2. 三次翻转法(经典原地算法,但字符串不可变,所以需要额外空间,但可以用于数组) 3. 取余法(循环移位,需要额外空间) 根据引用[1],还有暴力遍历法和库函数复制法(切片法就是利用库函数)。 因此,在实际编程中,我们通常使用切片法,因为它简洁高效。但如果要求原地操作(比如在C/C++中处理字符数组),则可以使用三次翻转法。 现在,我们回答用户的问题:字符串位移操作的实现方法主要有切片法、三次翻转法和取余法(循环移位法)。其中切片法最为简洁高效。 相关问题: 1. 字符串旋转操作的时间复杂度和空间复杂度分别是多少? 2. 除了左旋转,如何实现字符串的右旋转操作? 3. 在不能使用额外空间的情况下(例如C/C++中的字符数组),如何实现字符串旋转操作? 4. 字符串旋转操作有哪些实际应用场景?</think>### 字符串位移操作的实现方法 字符串位移操作(如左旋转)指将字符串前 k 个字符移动到末尾。以下是五种常用方法(以 Python 实现,输入示例:`s="abcdefg", k=2`,输出`"cdefgab"`): #### 1. **切片拼接法**(推荐) ```python def left_rotate_string(s, k): n = len(s) k = k % n # 处理 k > 字符串长度的情况 return s[k:] + s[:k] # 后半部分 + 前半部分 ``` - **原理**:直接截取子串拼接 - **复杂度**:$O(n)$ 时间,$O(n)$ 空间 - **示例**:`s[2:]="cdefg"` + `s[:2]="ab"` → `"cdefgab"` #### 2. **三次翻转法**(原地操作思想) ```python def left_rotate_string(s, k): n = len(s) k = k % n # 转为列表(因字符串不可变) s_list = list(s) # 1. 翻转前k个字符 s_list[0:k] = reversed(s_list[0:k]) # "ab" → "ba" # 2. 翻转剩余字符 s_list[k:] = reversed(s_list[k:]) # "cdefg" → "gfedc" # 3. 翻转整个字符串 s_list.reverse() # "bagfedc" → "cdefgab" return ''.join(s_list) ``` - **原理**:数学性质 $(A^T B^T)^T = BA$($A$ 为前 $k$ 个字符,$B$ 为剩余字符) - **复杂度**:$O(n)$ 时间,$O(n)$ 空间(因字符串不可变) #### 3. **循环取余法** ```python def left_rotate_string(s, k): n = len(s) k = k % n res = [] for i in range(n): # 新位置 i 对应原位置 (i+k) % n res.append(s[(i + k) % n]) return ''.join(res) ``` - **原理**:新字符串第 $i$ 位 = 原字符串第 $(i+k) \mod n$ 位 - **复杂度**:$O(n)$ 时间,$O(n)$ 空间 #### 4. **暴力遍历法** ```python def left_rotate_string(s, k): res = list(s) for _ in range(k): char = res.pop(0) # 移除首字符 res.append(char) # 添加到末尾 return ''.join(res) ``` - **原理**:逐个移动前 $k$ 个字符 - **复杂度**:$O(k \times n)$ 时间(低效),$O(n)$ 空间 #### 5. **队列优化法** ```python from collections import deque def left_rotate_string(s, k): dq = deque(s) dq.rotate(-k) # 负值表示左旋 return ''.join(dq) ``` - **原理**:利用双端队列的旋转操作 - **复杂度**:$O(n)$ 时间,$O(n)$ 空间 --- ### 方法对比 | 方法 | 时间复杂度 | 空间复杂度 | 适用场景 | |--------------|------------|------------|------------------------| | 切片拼接法 | $O(n)$ | $O(n)$ | 一般情况(推荐) | | 三次翻转法 | $O(n)$ | $O(n)$ | 需原地操作的场景 | | 循环取余法 | $O(n)$ | $O(n)$ | 理解循环移位原理 | | 暴力遍历法 | $O(kn)$ | $O(n)$ | 不推荐(性能差) | | 队列优化法 | $O(n)$ | $O(n)$ | 需快速实现 | > **应用场景**:字符串处理(如密码学)、数据压缩(如 Burrows-Wheeler 变换)[^1]。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值