剑指 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(2∗n)
- 空间复杂度:
O
(
n
)
O(n)
O(n)
双指针(空间O(n)
)
思路
- 重开个长度为 n n n 的数组空间 r e s res res;
- 初始化双指针
left = 0
,right = 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
target−i:
- 如果包含,则说明
{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.length−1;
left
从前向后遍历,right
从后向前遍历:- 若
nums[left] + nums[right] > target
,说明需要将当前两数之和 减小,即将right
向左移动; - 若
nums[left] + nums[right] < target
,说明需要将当前两数之和 增大,即将left
向右移动; - 若相等,则直接返回
left
、right
下标对应的元素。
- 若
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
+ 正则表达式
思路
- 去除首尾多余空格;
- 使用正则表达式,根据空格对剩余字符串进行 分割,得到单词 数组 arr;
- 将
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
是“不可变”的,这里仅仅是一种参考。
思路 🤔
- 去除多余空格
- 去掉开头多余空格;
- 去除单词间多余空格;
- 双指针保留单词字母
- 单词间 保留一个空格
- 去除单词间多余空格
- 如果
s
全是空格,即slow == 0
,则直接返回空串; - 去除末尾多余空格(其实已经在1.2里面去过了,现在末尾至多一个空格)
- 局部反转(单词内部)
- 整体反转
s[0, slow)
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)