双指针法
参考https://blog.csdn.net/qq_52595134/article/details/121385996
- 双指针法是用来优化循环的时间复杂度和空间复杂度的!什么时候需要优化时间复杂度!循环!遍历!特别是有条件的遍历!当我们使用两重循环时,时间复杂度一定是O(n2),空间复杂度根据算法而定。接下来看看,有哪些双指针方法呢?
对撞指针
左右两个指针,向中间靠拢。
快慢指针
两个指针,一快一慢
滑动窗口
左右两个指针组成一个"窗口",右指针不断扩张,左指针按条件收缩。
待补充…有空一定补上!
//交换两个变量的异或操作:只适用于数值
//不使用第三个变量,使用位操作:按位异或^:同0异1.
// 0和任何数做异或运算为此数本身,任何数与它自己做亦或为0.
num1 = num1 ^ num2;
num2 = num1 ^ num2; //num2 = num1^ num2 ^ num2 = num1
num1 = num1 ^ num2; //num1 = num1 ^ num2 ^ num1 = num2;
System.out.print("法三:再次交换后这两个变量的值是:" + num1 + " " + num2);
字符串
LeetCode 344. 反转字符串
编写一个函数,其作用是将输入的字符串反转过来。输入字符串以字符数组 s
的形式给出。
不要给另外的数组分配额外的空间,你必须**原地修改输入数组**、使用 O(1) 的额外空间解决这一问题。
输入:s = ["h","e","l","l","o"]
输出:["o","l","l","e","h"]
-
一开始的思路:
- 我最初想的问题是:字符串是什么数据结构存储的?是数组还是链表啊,我盲猜一个数组。(链表也不是不合理)
- 如果是数组的话,可以设置双指针,一个在头一个在尾,然后两个交换,分别移动一格,直到左指针>右指针。
- 如果是链表的话,就用一个空结点依次反转每个结点的指针
-
p.s. 刚想写,但是发现了一个巨大的问题,我忘记语法了,我甚至不知道指针怎么定义…
-
string.java中定义:private final char value[]; 是一个字符数组,用final修饰,意味着这个属性是一个常量,在初始化之后就不能再被修改。
-
错误:老生常谈的while条件,我写的left <= right…如果是双数个字母,只需要left < right,单数的话中间那个不用换,也只要left < right
LeetCode 541. 反转字符串 II
给定一个字符串 s
和一个整数 k
,从字符串开头算起,每计数至 2k
个字符,就反转这 2k
字符中的前 k
个字符。
- 如果剩余字符少于
k
个,则将剩余字符全部反转。 - 如果剩余字符小于
2k
但大于或等于k
个,则反转前k
个字符,其余字符保持原样。
输入:s = "abcdefg", k = 2
输出:"bacdfeg"
- 一开始的思路:是否可以先反转k个字母,然后再往后k个不反转,然后再往后k个反转。首先判断是否后面还有k个字母,如果没有则后面字母全部反转,如果有,就判断是否是需要反转的那k个字母
class Solution {
public String reverseStr(String s, int k) {
int move = 0;
int flag = 0;
char[] ch = s.toCharArray();
while(ch.length - move >= k){
if(flag % 2 == 0){
int start = move;
int end = move + k - 1;
while(start < end){
char temp = ch[start];
ch[start] = ch[end];
ch[end] = temp;
start++;
end--;
}
}
move = move + k;
flag = flag + 1;
}
if(flag % 2 == 0){
int start = move;
int end = ch.length - 1;
while(start < end){
char temp = ch[start];
ch[start] = ch[end];
ch[end] = temp;
start++;
end--;
}
}
return new String(ch);
}
}
- 代码巨长巨麻烦,我的思路就很多麻烦,因为要判断两次,第一次判断是不是大于k,第二次flag判断是否该反转。
- carl的思路是用for,每次考虑2k个,i直接2k移动,然后每次反转k的那段
- 怎么判断尾数够不够k?用?:来决定end到底是2k还是length-1
- .toCharArray()函数:将字符串转化为字符数组
- new String(ch):将字符数组转化为字符串
class Solution {
public String reverseStr(String s, int k) {
char[] ch = s.toCharArray();
for(int i = 0; i < ch.length; i += 2*k){
int start = i;
//这是判断剩多少字母,如果i + k - 1的话,则有k< <2k个元素,如果ch.length - 1的话,则<k个元素
int end = Math.min(i + k - 1, ch.length - 1);
//此时我们得到的end就是要反转的字母个数
while(start < end){
char temp = ch[start];
ch[start] = ch[end];
ch[end] = temp;
start++;
end--;
}
}
return new String(ch);
}
}
LeetCode 剑指 Offer 05. 替换空格
请实现一个函数,把字符串 s
中的每个空格替换成"%20"。
输入:s = "We are happy."
输出:"We%20are%20happy."
-
一开始的思路:我有个问题,怎么判断是不是空格啊?看ascii码吗?笑死,这样就行:if (s[i] == ’ ')
-
遍历就好,从头开始,遇到空格就替换。但是有个问题,空格占一个格子,%20占三个格子,我发现一个空格就得把后面所有字符全挪一遍,时间复杂度很高,O(n2)
-
这时候我就去看题解了,告诉我我得从后往前遍历。为什么呢?
-
因为双指针法!我们回忆一下,双指针法一般在什么情况下使用呢?(我忘了,我去翻笔记去)(我翻完了,我发现我妹写,笑死,那我现在写写,写在最前面)
-
从后向前遍历,首先扩充数组到每个空格替换成"%20"之后的大小。两个指针一个是新结尾的位置,一个是原结尾的位置,然后同时向前移动。如果左指针遇到了空格,那就把右指针挪k格,
-
Java StringBuffer 和 StringBuilder 类:当对字符串进行修改的时候,需要使用 StringBuffer 和 StringBuilder 类。和 String 类不同的是,StringBuffer 和 StringBuilder 类的对象能够被多次的修改,并且不产生新的未使用对象。(string.java中定义:private final char value[]; 是一个字符数组,用final修饰,意味着这个属性是一个常量,在初始化之后就不能再被修改。)
-
注意’ '和" "的区别:一个是字符一个是字符串
-
替换’ '时,注意是添加两个空格而不是三个!只额外多了两个字符!不然出来就是wewe are happy.
class Solution {
public static String replaceSpace(String s) {
StringBuilder str = new StringBuilder();
for(int i = 0; i < s.length(); i++){
if(s.charAt(i) == ' '){
str.append(" ");
}
}
//若是没有空格直接返回
if(str.length() == 0){
return s;
}
int left = s.length() - 1;
//在s后面添加需要替换的空间
s += str.toString();
int right = s.length() - 1;//左指针:指向原始字符串最后一个位置
char[] chars = s.toCharArray();
while(left >= 0){
if(chars[left] == ' '){
chars[right--] = '0';
chars[right--] = '2';
chars[right] = '%';
}else{
//把字母往后挪
chars[right] = chars[left];
}
left--;
right--;
}
return new String(chars);
}
}
LeetCode 151.翻转字符串里的单词
给你一个字符串 s
,请你反转字符串中 单词 的顺序。
单词 是由非空格字符组成的字符串。s
中使用至少一个空格将字符串中的 单词 分隔开。
返回 单词 顺序颠倒且 单词 之间用单个空格连接的结果字符串。
**注意:**输入字符串 s
中可能会存在前导空格、尾随空格或者单词间的多个空格。返回的结果字符串中,单词间应当仅用单个空格分隔,且不包含任何额外的空格。
the sky is blue"
输出:"blue is sky the
- 一开始的思路:双指针, 从头开始,快指针指到空格时,就把快慢的中间翻转。问题在于无法处理多余空格或者连续的几个空格。
- 题解:
- 移除多余空格
- 将整个字符串反转
- 将每个单词反转
- 移除多余空格
- 设置快慢指针,快指针指向要判断是否为空格的字符,慢指针指向下一个被添加的字符。如果快指针为空格,则后移,fast所指不为空时,判断slow是不是第一个单词,是则不动,不是则要添加一个空格,因为单词之间需要存在一个空格。然后fast每个值都赋值给slow,直到fast再次遇到空格,然后循环。
- 将整个字符串反转
- 用双指针对撞指针反转字符串,参考344
- 将每个单词反转
- 设置前后两个指针,后指针先移动,直到遇到空格,然后把前后指针之间的字符串反转。
class Solution {
//用 char[] 来实现 String 的 removeExtraSpaces,reverse 操作
public String reverseWords(String s) {
char[] chars = s.toCharArray();
//1.去除首尾以及中间多余空格
chars = removeExtraSpaces(chars);
//2.整个字符串反转
reverse(chars, 0, chars.length - 1);
//3.单词反转
reverseEachWord(chars);
return new String(chars);
}
//1.用 快慢指针 去除首尾以及中间多余空格,可参考数组元素移除的题解
public char[] removeExtraSpaces(char[] chars) {
int slow = 0;
for (int fast = 0; fast < chars.length; fast++) {
//先用 fast 移除所有空格
if (chars[fast] != ' ') {//fast所指不为空时
//在用 slow 加空格。 除第一个单词外,单词末尾要加空格
//如果slow为0,证明是第一个单词,第一个单词前面不需要加空格
//如果slow不为0,证明前面已经有单词了,所以要在前一个单词后面加空格
if (slow != 0){
chars[slow++] = ' ';
}
//fast 遇到空格或遍历到字符串末尾,就证明遍历完一个单词了
while (fast < chars.length && chars[fast] != ' ')
chars[slow++] = chars[fast++];
}
}
//相当于 c++ 里的 resize()
char[] newChars = new char[slow];
//arrayCopy( arr1, 2, arr2, 5, 10)意思是将arr1数组里从索引为2的元素开始,复制到数组arr2里的索引为5的位置, 复制的元素个数为10个.
System.arraycopy(chars, 0, newChars, 0, slow);
return newChars;
}
//双指针实现指定范围内字符串反转,可参考字符串反转题解
public void reverse(char[] chars, int left, int right) {
if (right >= chars.length) {
System.out.println("set a wrong right");
return;
}
while (left < right) {
chars[left] ^= chars[right];
chars[right] ^= chars[left];
chars[left] ^= chars[right];
left++;
right--;
}
}
//3.单词反转
public void reverseEachWord(char[] chars) {
int start = 0;
//end <= s.length() 这里的 = ,是为了让 end 永远指向单词末尾后一个位置,这样 reverse 的实参更好设置
for (int end = 0; end <= chars.length; end++) {
// end 每次到单词末尾后的空格或串尾,开始反转单词
if (end == chars.length || chars[end] == ' ') {
reverse(chars, start, end - 1);
start = end + 1;
}
}
}
}
LeetCode 剑指 Offer 58 - II. 左旋转字符串
字符串的左旋转操作是把字符串前面的若干个字符转移到字符串的尾部。请定义一个函数实现字符串左旋转操作的功能。比如,输入字符串"abcdefg"和数字2,该函数将返回左旋转两位得到的结果"cdefgab"。
输入: s = "abcdefg", k = 2
输出: "cdefgab"
- 一开始的思路:以k个为一组看作一个整体,然后不停的swap直到最后。有一个问题,比如说后面的字母不够怎么办。
- 看了题解,也是采用翻转再翻转的策略。
- [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PfksHQt4-1677248581514)(https://code-thinking.cdn.bcebos.com/pics/%E5%89%91%E6%8C%87Offer58-II.%E5%B7%A6%E6%97%8B%E8%BD%AC%E5%AD%97%E7%AC%A6%E4%B8%B2.png)]
- 反转区间为前n的子串
- 反转区间为n到末尾的子串
- 反转整个字符串
class Solution {
public String reverseLeftWords(String s, int n) {
char[] chars = s.toCharArray();
reverse(chars, 0, chars.length - 1);
reverse(chars, 0, chars.length - 1 - n);
reverse(chars, chars.length - n, chars.length - 1);
return new String(chars);
}
public void reverse(char[] chars, int left, int right) {
while (left < right) {
chars[left] ^= chars[right];
chars[right] ^= chars[left];
chars[left] ^= chars[right];
left++;
right--;
}
}
}
还剩KMP没看,二刷再看
总结
-
反转字符串使用双指针法,对撞指针。交换两个位置的数值可以用异或运算。
//交换a,b的值 a ^= b b ^= a a ^= b
-
替换空格注意从后往前,使用双指针,提前留出需要添加的看空间
-
有条件的翻转字符串可以考虑翻转再翻转,先局部反转再整体反转