《剑指offer》 -day13- 双指针(简单)

剑指 Offer 21. 调整数组顺序使奇数位于偶数前面

题目描述

输入一个整数数组,实现一个函数来调整该数组中数字的顺序,使得所有奇数在数组的前半部分,所有偶数在数组的后半部分。

提示:

  • 0 < = n u m s . l e n g t h < = 50000 0 <= nums.length <= 50000 0<=nums.length<=50000
  • 0 < = n u m s [ i ] < = 10000 0 <= nums[i] <= 10000 0<=nums[i]<=10000

暴力(两遍遍历)

思路

  • 重开个长度为 n n n 的数组空间 r e s res res
  • 首先,从头遍历 将 n u m s nums nums 中所有 奇数,依次添加到 r e s res res 中;
  • 然后,再将 所有偶数,添加到 res 中(保证了偶数都在奇数后面)。
class Solution {
    public int[] exchange(int[] nums) {
        int n = nums.length;
        int[] res = new int[n];
        int i = 0;
        // 奇数
        for (int j = 0; j < n; j++) {
            if (nums[j] % 2 == 1) res[i++] = nums[j];
        }
        // 偶数
        for (int j = 0; j < n; j++) {
            if (nums[j] % 2 == 0) res[i++] = nums[j];
        }
        return res;
    }
}
  • 时间复杂度: O ( 2 ∗ n ) O(2 * n) O(2n)
  • 空间复杂度: O ( n ) O(n) O(n)

双指针(空间O(n)

思路

  • 重开个长度为 n n n 的数组空间 r e s res res
  • 初始化双指针 left = 0right = n - 1
  • 从前向后遍历 nums
    • 如果当前元素为奇数,则填充 res[left],并将 left 右移一位;
    • 否则,则填充 res[right],并将 right 左移一位
class Solution {
    public int[] exchange(int[] nums) {
        int n = nums.length;
        int[] res = new int[n];
        int left = 0;
        int right = n - 1;
        for (int i = 0; i < n; i++) {
            if (nums[i] % 2 == 1) {
                res[left++] = nums[i];
            } else {
                res[right--] = nums[i];
            }
        }
        return res;
    }
}
  • 时间复杂度: O ( n ) O(n) O(n)
  • 空间复杂度: O ( n ) O(n) O(n)
    在这里插入图片描述

双指针(空间O(1))⭐️

采用类似 快速排序-划分区间的思想,参考 K佬题解

思路 🤔

  • 定义双指针:left 左边的都是奇数;right 右边的都是偶数
  • 从左向右遍历,遇到奇数直接跳过,直到找到 第一个偶数后,退出(此时,left 指向 偶数)
  • 从右向左遍历,遇到偶数直接跳过,直到找到 第一个奇数后,退出(此时,right 指向 奇数)
  • 交换 二者后,继续遍历,直至 left < right 不成立后 退出。
class Solution {
    public int[] exchange(int[] nums) {
        int n = nums.length;
        int left = 0;
        int right = n - 1;
        // 只有一个元素时,没必要交换,所以不用带等号
        while (left < right) {
            // left从左向右,找“偶数”则停止
            while (left < right && (nums[left] & 1) == 1) {
                left++;
            }
            // right从右向左,找“奇数”则停止
            while (left < right && (nums[right] & 1) == 0) {
                right--;
            }
            System.out.println("left = " + left);
            System.out.println("right = " + right);
            // 交换
            swap(nums, left, right);
        }
        return nums;
    }

    void swap(int[] nums, int x, int y) {
        if (nums[x] != nums[y]) { // 一定要不相等;否则,交换结果为0
            // 交换二者
            nums[x] = nums[x] ^ nums[y];
            nums[y] = nums[x] ^ nums[y];
            nums[x] = nums[x] ^ nums[y];
        }
    }
}

说明:

  • 通过位运算 (nums[left] & 1) == 1 判断“奇数” 和 (nums[left] % 2) == 1 作用一致;
  • swap 方法中通过位运算 来交换两个数 和 常用的使用中间变量 temp 的作用一致。
  • 时间复杂度: O ( n ) O(n) O(n)
  • 空间复杂度: O ( 1 ) O(1) O(1)
    双指针

剑指 Offer 57. 和为s的两个数字

题目描述

输入一个递增排序的数组和一个数字s,在数组中查找两个数,使得它们的和正好是s。如果有多对数字的和等于s,则输出任意一对即可。

限制:

  • 1 < = n u m s . l e n g t h < = 1 0 5 1 <= nums.length <= 10^5 1<=nums.length<=105
  • 1 < = n u m s [ i ] < = 1 0 6 1 <= nums[i] <= 10^6 1<=nums[i]<=106

暴力

题目数据量为 1 < = n u m s . l e n g t h < = 1 0 5 1 <= nums.length <= 10^5 1<=nums.length<=105,如果使用 2 层 for 纯暴力的话,直接 TLE.

// TLE code
class Solution {
    public int[] twoSum(int[] nums, int target) {
        int n = nums.length;
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < n; j++) {
                if (j == i) continue;
                if (nums[i] + nums[j] == target) {
                    return new int[] {nums[i], nums[j]};
                }
            }
        }
        return new int[] {};
    }
}
  • 时间复杂度: O ( n 2 ) O(n^2) O(n2) (超时)
  • 空间复杂度: O ( 1 ) O(1) O(1)
    暴力

哈希

类似 两数之和,可以考虑使用 哈希.

思路

  • 使用 s e t set set 保存 n u m s nums nums 中已经遍历过的元素;
  • 每次迭代时,检查 s e t set set 中是否包含 t a r g e t − i target - i targeti
    • 如果包含,则说明 {i, target - i} 即为所求结果;
    • 否则,继续迭代
class Solution {
    public int[] twoSum(int[] nums, int target) {
        int n = nums.length;
        HashSet<Integer> set = new HashSet<>();
        for (int i : nums) {
            if (set.contains(target - i)) {
                return new int[] {i, target - i};
            }
            set.add(i);
        }
        return new int[] {};
    }
}
  • 时间复杂度: O ( n ) O(n) O(n)
  • 空间复杂度: O ( n ) O(n) O(n)
    哈希

双指针

以上使用 哈希 进行求解,对于数组是否 有序没有要求。
但是,本题中说到 该数组是递增排序,可以考虑使用 双指针 求解。(思想类似于 三数之和

思路 🤔

  • 初始化双指针: l e f t = 0 ; left = 0; left=0; r i g h t = n u m s . l e n g t h − 1 ; right = nums.length - 1; right=nums.length1;
  • left 从前向后遍历,right 从后向前遍历:
    • nums[left] + nums[right] > target,说明需要将当前两数之和 减小,即将 right 向左移动;
    • nums[left] + nums[right] < target,说明需要将当前两数之和 增大,即将 left 向右移动;
    • 若相等,则直接返回 leftright下标对应的元素。
class Solution {
    public int[] twoSum(int[] nums, int target) {
        int left = 0;
        int right = nums.length - 1;
        while (left <= right) {
            if (nums[left] + nums[right] < target) {
                left++;
            } else if (nums[left] + nums[right] > target) {
                right--;
            } else {
                return new int[] {nums[left], nums[right]};
            }
        }
        return null;
    }
}
  • 时间复杂度: O ( n ) O(n) O(n)
  • 空间复杂度: O ( 1 ) O(1) O(1)
    双指针

剑指 Offer 58 - I. 翻转单词顺序

题目描述

输入一个英文句子,翻转句子中单词的顺序,但单词内字符的顺序不变。为简单起见,标点符号和普通字母一样处理。例如输入字符串"I am a student. “,则输出"student. a am I”。

说明:

  • 无空格字符构成一个单词。
  • 输入字符串可以在前面或者后面包含多余的空格,但是反转后的字符不能包括。
  • 如果两个单词间有多余的空格,将反转后单词间的空格减少到只含一个。

StringBuilder + 正则表达式

思路

  1. 去除首尾多余空格;
  2. 使用正则表达式,根据空格对剩余字符串进行 分割,得到单词 数组 arr;
  3. arr 倒序添加至 sb
class Solution {
    public String reverseWords(String s) {
        String[] arr = s.trim().split("\\s+");
        StringBuilder sb = new StringBuilder();
        for (int i = arr.length - 1; i >= 0; i--) {
            sb.append(arr[i]);
            if (i > 0) sb.append(" ");
        }
        return sb.toString();
    }
}
  • 时间复杂度: O ( n ) O(n) O(n)
  • 空间复杂度: O ( n ) O(n) O(n)
    在这里插入图片描述

双指针

C++ 中的 string 是可变的,所以可以将 空间复杂度降为 O ( 1 ) O(1) O(1).
但是,Java 中的 String 是“不可变”的,这里仅仅是一种参考。

思路 🤔

  1. 去除多余空格
    • 去掉开头多余空格;
    • 去除单词间多余空格;
      • 双指针保留单词字母
      • 单词间 保留一个空格
      • 去除单词间多余空格
    • 如果 s 全是空格,即 slow == 0,则直接返回空串;
    • 去除末尾多余空格(其实已经在1.2里面去过了,现在末尾至多一个空格)
  2. 局部反转(单词内部)
  3. 整体反转 s[0, slow)
  4. arr[0, slow) to String
import java.util.Arrays;

/**
 * @author: psoper
 * @version: v1.0
 * @date: 2022/04/13 13:03
 **/
class Solution {
    public String reverseWords(String s) {
        char[] arr = s.toCharArray();
        int n = s.length();
        int fast = 0;
        int slow = 0;
        // 1.1、去掉开头多余空格
        while (fast < n && arr[fast] == ' ') {
            fast++;
        }
        // 1.2、去除单词间多余空格
        while (fast < n) {
            // 1.2.1、双指针保留单词字母
            while (fast < n && arr[fast] != ' ') {
                arr[slow++] = arr[fast++];
            }
            // 1.2.2、单词间 保留一个空格
            if (fast < n) arr[slow++] = arr[fast++];
            // 1.2.3、去除单词间多余空格
            while (fast < n && arr[fast] == ' ') {
                fast++;
            }
        }
        // 全是空格
        if (slow == 0) {
            return "";
        }
        // 1.3、去除末尾多余空格(其实已经在1.2里面去过了,现在末尾至多一个空格)
        if (arr[slow - 1] == ' ') {
            slow--;
        }
        // 2、局部反转(单词内部)
        for (int i = 0; i < slow; i++) {
            int left = i;
            while (i < slow && arr[i] != ' ') {
                i++;
            }
            reverse(arr, left, i); // 左闭右开
        }
        // 3、整体反转
        reverse(arr, 0, slow);
        // arr[0, slow) to String
        return new String(Arrays.copyOfRange(arr, 0, slow));
    }

    // 左闭右开区间:[x, y)
    void reverse(char[] arr, int x, int y) {
        int left = x;
        int right = y - 1;
        while (left < right) {
            char temp = arr[left];
            arr[left] = arr[right];
            arr[right] = temp;
            // 更新
            left++;
            right--;
        }
    }

    public static void main(String[] args) {
        String s = "the sky is blue";
        System.out.println(new Solution().reverseWords(s));
    }
}

  • 时间复杂度: O ( n ) O(n) O(n)
  • 空间复杂度:如果忽略 arr 数组 和 最终 new String 的空间,则空间使用为 O ( 1 ) O(1) O(1)。但其实是 O ( n ) O(n) O(n)
    在这里插入图片描述
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值