本题出自https://leetcode.cn/problems/reverse-string/description/,属于是最最最简单的一档题目了,但也需要一些耐心才能开始算法的学习对吧,千里之行始于足下,不积跬步无以至千里。
编写一个函数,其作用是将输入的字符串反转过来。输入字符串以字符数组
s
的形式给出。不要给另外的数组分配额外的空间,你必须原地修改输入数组、使用 O(1) 的额外空间解决这一问题。
示例 1:
输入:s = ["h","e","l","l","o"] 输出:["o","l","l","e","h"]示例 2:
输入:s = ["H","a","n","n","a","h"] 输出:["h","a","n","n","a","H"]提示:
1 <= s.length <= 105
s[i]
都是 ASCII 码表中的可打印字符
双指针
首先我们排除例如C语言库中reverse这种直接反转的库函数的调用,相信会写这道题的同学还属于刚开始的阶段,所以优先选择例如双指针的用法,更加基础且切合出题者出这道题的意图
左指针left我们定义为i,这样一来i就代表着左边的数;
右指针right我们定义为j,这样一来j就代表着右边的数;
数组是s,s[i] 和 s[j] 就代表着数组中的数了
C++的解法
class Solution {
public:
void reverseString(vector<char>& s) {
for (int i = 0, j = s.size() - 1; i < s.size()/2; i++, j--) {
swap(s[i],s[j]);
}
}
};
Java的解法
class Solution {
public void reverseString(char[] s) {
int i=0;
int j=s.length-1;
while (i<j){
char temp=s[i];
s[i]=s[j];
s[j]=temp;
i++;
j--;
}
}
}
Python的解法
class Solution(object):
def reverseString(self, s):
"""
:type s: List[str]
:rtype: None Do not return anything, modify s in-place instead.
"""
i, right = 0, len(s) - 1
while i < j:
s[i], s[j] = s[j], s[i]
i += 1
j -= 1
双指针的综合运用
双指针技术是一种常用的算法技巧,尤其适用于处理数组和链表问题。通过使用两个指针,可以在一次遍历中完成某些复杂操作,从而提高算法的效率。常见的双指针应用场景包括:
- 数组/链表的两两配对:如反转数组、查找数组中的特定元素对等。
- 滑动窗口:用于解决子数组或子串问题,如最大子数组和、最小覆盖子串等。
- 快慢指针:用于检测链表中的环、寻找链表的中间节点等。
常见的双指针应用场景
1. 反转数组
我们已经在之前的示例中看到了如何使用双指针来反转数组。这里再简单回顾一下:(以下代码为Java代码以作演示)
class Solution {
public void reverseString(char[] s) {
int i = 0;
int j = s.length - 1;
while (i < j) {
char temp = s[i];
s[i] = s[j];
s[j] = temp;
i++;
j--;
}
}
}
在这个例子中,i
和 j
分别从数组的两端向中间移动,交换它们所指向的元素,直到 i
和 j
相遇或交错。
2. 查找数组中的特定元素对
例如,给定一个排序数组和一个目标值,找到数组中两个数的和等于目标值的索引。
class Solution {
public int[] twoSum(int[] numbers, int target) {
int i = 0;
int j = numbers.length - 1;
while (i < j) {
int sum = numbers[i] + numbers[j];
if (sum == target) {
return new int[]{i + 1, j + 1}; // 题目要求返回索引从1开始
} else if (sum < target) {
i++;
} else {
j--;
}
}
return new int[]{-1, -1}; // 如果没有找到,返回默认值
}
}
在这个例子中,i
和 j
分别从数组的两端向中间移动,根据 numbers[i] + numbers[j]
与目标值的比较结果调整指针的位置。
3. 滑动窗口
例如,找到一个数组中最长的连续子数组,使得该子数组的和不超过某个给定的值。
class Solution {
public int maxSubArrayLen(int[] nums, int k) {
int maxLength = 0;
int currentSum = 0;
int start = 0;
for (int end = 0; end < nums.length; end++) {
currentSum += nums[end];
while (currentSum > k && start <= end) {
currentSum -= nums[start];
start++;
}
maxLength = Math.max(maxLength, end - start + 1);
}
return maxLength;
}
}
在这个例子中,start
和 end
分别表示滑动窗口的左右边界。通过调整窗口的大小,确保窗口内的元素和不超过给定的值 k
。
4. 快慢指针
例如,检测链表中是否存在环。
class ListNode {
int val;
ListNode next;
ListNode(int x) { val = x; }
}
class Solution {
public boolean hasCycle(ListNode head) {
if (head == null || head.next == null) {
return false;
}
ListNode slow = head;
ListNode fast = head.next;
while (slow != fast) {
if (fast == null || fast.next == null) {
return false;
}
slow = slow.next;
fast = fast.next.next;
}
return true;
}
}
在这个例子中,slow
指针每次移动一步,fast
指针每次移动两步。如果存在环,fast
指针最终会追上 slow
指针。
总结
双指针技术通过使用两个指针来解决问题,可以有效地减少时间和空间复杂度。不同的应用场景需要灵活选择合适的指针移动策略。
本题的一种特殊解法,无需引入变量,按位异或运算
按位异或,^
是位运算符
在 Java 中,^
是一个位运算符,称为“按位异或”(Bitwise XOR)。它用于对两个整数类型的值进行逐位比较,当相应位上两个操作数的值不同时结果为1,相同时结果为0。
例如,如果有两个整数 a = 5
(二进制表示为 0101
) 和 b = 3
(二进制表示为 0011
),那么 a ^ b
的计算过程如下:
0101 (a)
^ 0011 (b)
------
0110 (结果为6)
因此,a ^ b
的结果是 6
(0110)。
按位异或运算符在多种场景下都有应用,比如数据加密、交换两个变量的值而不需要额外的临时变量等。
解法
int i = 0;
int j = s.length - 1;
while (i < j) {
s[i] ^= s[j];
s[j] ^= s[i];
s[i] ^= s[j];
i++;
j--;
}
说明
这三行代码利用了按位异或运算的特性来交换
s[i]
和s[j]
的值,而不需要额外的临时变量。具体步骤如下:
- 第一步:
s[i] ^= s[j]
,将s[i]
和s[j]
的异或结果存储在s[i]
中。- 第二步:
s[j] ^= s[l]
,将第一步的结果与s[j]
异或,结果存储在s[j]
中,此时s[j]
存储的是原来的s[i]
的值。- 第三步:
s[i] ^= s[j]
,将第二步的结果与s[j]
异或,结果存储在s[i]
中,此时s[i]
存储的是原来的s[j]
的值。 经过这三步操作后,s[i]
和s[j]
的值就完成了交换。