目录
双指针
1 奇数调整到偶数前面
1. 双指针(自己写的)
package jzof.Day13;
/**
* @author ahan
* @create_time 2021-11-13-8:12 下午
* 输入一个整数数组,实现一个函数来调整该数组中数字的顺序,使得所有奇数在数组的前半部分,所有偶数在数组的后半部分。
*/
public class _21 {
public static void main(String[] args) {
// int [] nums = new int[]{1,2,3,4};
int [] nums = new int[]{11,9,3,7,16,4,2,0};
// int [] nums = new int[]{1,3,5};
int[] exchange = new _21().exchange(nums);
for (int i = 0; i < exchange.length; i++) {
System.out.println(exchange[i]);
}
}
public int[] exchange(int[] nums) {
int left = 0, right = nums.length - 1;
while (left<right){
if(nums[left] % 2 == 0 && nums[right] % 2 == 1){
// a = a + b;
// b = a - b;
// a = a - b;
// nums[left] = nums[left] + nums[right];
// nums[right] = nums[left] - nums[right];
// nums[left] = nums[left] - nums[right];
// a = a^b; //赋值表达式先不动
// b = a^b; //b = (a^b)^b; b值发生的改变为a的值;
// a = b^a; // a = a^(a^b) = b
// nums[left] = nums[left] ^ nums[right];
// nums[right] = nums[left] ^ nums[right];
// nums[left] = nums[right] ^ nums[left];
int t = nums[left];
nums[left] = nums[right];
nums[right] = t;
left++;
right--;
}
if(nums[left] % 2 != 0)
left++;
if(nums[right] % 2 != 1)
right--;
}
return nums;
}
}
类似的可以改为如下:
class Solution {
public int[] exchange(int[] nums) {
int left = 0, right = nums.length - 1;
while (left<right){
if(nums[left] % 2 != 0){
left++;
continue;
}
if(nums[right] % 2 != 1){
right--;
continue;
}
int t = nums[left];
nums[left] = nums[right];
nums[right] = t;
}
return nums;
}
}
2. 双指针 + 循环找到目标索引
把判断是前半部分的奇数 和 后半部分的偶数 节点++ 改为 while 这样减少了很多循环不必要的循环。
class Solution {
public int[] exchange(int[] nums) {
int left = 0, right = nums.length - 1;
while (left<right){
while(left < right && (nums[left] & 1) == 1)
left++;
while(left < right && (nums[right] & 1) == 0)
right--;
int t = nums[left];
nums[left] = nums[right];
nums[right] = t;
}
return nums;
}
}
x&1 位运算 等价于 x%2 取余运算,即可用于判断数字奇偶性。
复杂度分析:
- 时间复杂度 O(N): N 为数组 nums 长度,双指针 i, j 共同遍历整个数组。
- 空间复杂度 O(1): 双指针 i, j 使用常数大小的额外空间。
3. 快慢指针
运用快排的思想。
- 定义快慢双指针 fast 和 low ,fast 在前, low 在后 .
- fast 的作用是向前搜索奇数位置,low 的作用是指向下一个奇数应当存放的位置
- fast 向前移动,当它搜索到奇数时,将它和 nums[low] 交换,此时 low 向前移动一个位置 .
- 重复上述操作,直到 fast 指向数组末尾 .
class Solution {
public int[] exchange(int[] nums) {
int low = 0, fast = 0;
while (fast < nums.length) {
if ((nums[fast] & 1) == 1) {
int t = nums[low];
nums[low] = nums[fast];
nums[fast] = t;
low ++;
}
fast ++;
}
return nums;
}
}
2 求和为s的两个数字
剑指 Offer 57. 和为s的两个数字https://leetcode-cn.com/problems/he-wei-sde-liang-ge-shu-zi-lcof/
1. 双指针
解题思路:
利用 HashMap 可以通过遍历数组找到数字组合,时间和空间复杂度均为 O(N) ;
注意本题的 nums 是 排序数组 ,因此可使用 双指针法 将空间复杂度降低至 O(1) 。
算法流程:
- 初始化: 双指针 i , j 分别指向数组 numsnums 的左右两端 (俗称对撞双指针)。
- 循环搜索: 当双指针相遇时跳出;
- 计算和 s = nums[i] + nums[j] ;
- 若 s > target ,则指针 j 向左移动,即执行 j = j - 1 ;
- 若 s < target ,则指针 i 向右移动,即执行 i = i + 1 ;
- 若 s = target ,立即返回数组 [nums[i], nums[j]];
- 返回空数组,代表无和为 target 的数字组合。
class Solution {
public int[] twoSum(int[] nums, int target) {
int start = 0;
// int end = bindFind(nums, 9);
int end = nums.length - 1;
while(start < end ){
int sum = nums[start] + nums[end];
if(sum > target)
end--;
else if (sum == target)
return new int[]{nums[start], nums[end]};
else
start++;
}
return nums;
}
}
2. 二分搜索+双指针
可以二分先缩小范围,再去双撞指针
class Solution {
public int[] twoSum(int[] nums, int target) {
int start = 0;
int end = bindFind(nums, target);
// int end = nums.length - 1;
while(start < end ){
int sum = nums[start] + nums[end];
if(sum > target)
end--;
else if (sum == target)
return new int[]{nums[start], nums[end]};
else
start++;
}
return nums;
}
public int bindFind(int[] nums, int target){
int i = 0, j = nums.length - 1;
int mid = 0;
while(i <= j){
mid = ((j - i) >> 1) + i;
if(target < nums[mid])
j = mid -1;
else if (target == nums[mid])
return mid;
else
i = mid + 1;
}
return mid;
}
}
3 翻转单词顺序
剑指 Offer 58 - I. 翻转单词顺序https://leetcode-cn.com/problems/fan-zhuan-dan-ci-shun-xu-lcof/
1. 双指针
算法解析:
- 倒序遍历字符串 s ,记录单词左右索引边界 i , j ;
- 每确定一个单词的边界,则将其添加至单词列表 res ;
- 最终,将单词列表拼接为字符串,并返回即可。
复杂度分析:
- 时间复杂度 O(N) : 其中 N 为字符串 s 的长度,线性遍历字符串。
- 空间复杂度 O(N) : 新建的 list(Python) 或 StringBuilder(Java) 中的字符串总长度 ≤N ,占用 O(N) 大小的额外空间。
class Solution {
public String reverseWords(String s) {
s = s.trim(); // 删除首尾空格
int j = s.length() - 1, i = j;
StringBuilder res = new StringBuilder();
while(i >= 0) {
while(i >= 0 && s.charAt(i) != ' ') i--; // 搜索首个空格
res.append(s.substring(i + 1, j + 1) + " "); // 添加单词
while(i >= 0 && s.charAt(i) == ' ') i--; // 跳过单词间空格
j = i; // j 指向下个单词的尾字符
}
return res.toString().trim(); // 转化为字符串并返回
}
}
2. 双指针(单词间多个空格使用replaceAll)
第一个不过的例子是尾部多一个空格 第二个是两边需要trim() 第三个是连续空格 连续空格处理不掉 因为我是根据遇到空格判定 所以用到了replaceALL
class Solution {
public String reverseWords(String s) {
s = s.replaceAll("\\s{2,}", " ")+' ';
int i = s.length()-1;
int start = 0, end = s.length()-1;
StringBuilder sb = new StringBuilder();
while(i >= 0){
if(' ' == s.charAt(i)){
start = i+1;
// System.out.println(start + " " +end);
for(int j = start; j <= end; j++)
sb.append(s.charAt(j));
end = start - 1;
}
i--;
}
System.out.println(end+" ");
for(int j = 0; j <= end; j++)
sb.append(s.charAt(j));
return sb.substring(0,sb.length()-1).trim();
}
}
3.分割 + 倒序
利用 “字符串分割”、“列表倒序” 的内置函数 (面试时不建议使用) ,可简便地实现本题的字符串翻转要求。
复杂度分析:
- 时间复杂度 O(N): 总体为线性时间复杂度,各函数时间复杂度和参考资料链接如下。
- split() 方法: 为 O(N) ;
- trim() 和 strip() 方法: 最差情况下(当字符串全为空格时),为 O(N) ;
- join() 方法: 为 O(N) ;
- reverse() 方法: 为 O(N) ;
- 空间复杂度 O(N) : 单词列表 strsstrs 占用线性大小的额外空间。
class Solution {
public String reverseWords(String s) {
String[] strs = s.trim().split(" "); // 删除首尾空格,分割字符串
StringBuilder res = new StringBuilder();
for(int i = strs.length - 1; i >= 0; i--) { // 倒序遍历单词列表
if(strs[i].equals("")) continue; // 遇到空单词则跳过
res.append(strs[i] + " "); // 将单词拼接至 StringBuilder
}
return res.toString().trim(); // 转化为字符串,删除尾部空格,并返回
}
}
Python : 由于 split()split() 方法将单词间的 “多个空格看作一个空格” (参考自 split()和split(' ')的区别 ),因此不会出现多余的 “空单词” 。因此,直接利用 reverse()reverse() 方法翻转单词列表 strsstrs ,拼接为字符串并返回即可。
class Solution:
def reverseWords(self, s: str) -> str:
return ' '.join(s.strip().split()[::-1])