阿翰 剑指offer 之 Day 13 双指针 3

目录

双指针

1 奇数调整到偶数前面

1. 双指针(自己写的)

2. 双指针 + 循环找到目标索引

3. 快慢指针

2 求和为s的两个数字

1. 双指针

2. 二分搜索+双指针 

3 翻转单词顺序

1. 双指针

2. 双指针(单词间多个空格使用replaceAll) 

3.分割 + 倒序


双指针

1 奇数调整到偶数前面

剑指 Offer 21. 调整数组顺序使奇数位于偶数前面https://leetcode-cn.com/problems/diao-zheng-shu-zu-shun-xu-shi-qi-shu-wei-yu-ou-shu-qian-mian-lcof/

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) 。

算法流程:

  1. 初始化: 双指针 i , j 分别指向数组 numsnums 的左右两端 (俗称对撞双指针)
  2. 循环搜索: 当双指针相遇时跳出;
    1. 计算和 s = nums[i] + nums[j] ;
    2. 若 s > target ,则指针 j 向左移动,即执行 j = j - 1  ;
    3. 若 s < target  ,则指针 i 向右移动,即执行 i = i + 1 ;
    4. 若 s = target  ,立即返回数组 [nums[i], nums[j]];
  3. 返回空数组,代表无和为 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.分割 + 倒序

利用 “字符串分割”、“列表倒序” 的内置函数 (面试时不建议使用) ,可简便地实现本题的字符串翻转要求。

复杂度分析:

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 可一行实现

Python : 由于 split()split() 方法将单词间的 “多个空格看作一个空格” (参考自 split()和split(' ')的区别 ),因此不会出现多余的 “空单词” 。因此,直接利用 reverse()reverse() 方法翻转单词列表 strsstrs ,拼接为字符串并返回即可。 

class Solution:
    def reverseWords(self, s: str) -> str:
        return ' '.join(s.strip().split()[::-1])

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值