上一篇文章,我们看了数组移除元素的问题,了解了双指针法,这一篇继续刷题。
有序数组的平方
给你一个按 非递减顺序 排序的整数数组
nums,返回 每个数字的平方 组成的新数组,要求也按 非递减顺序 排序。
解决这道题,可以分成两步:
⨳ 遍历数组nums,将每个数字开平方,时间复杂度为 O(n)
⨳ 对开平方后的数组进行排序,如果使用快排的话,时间复杂度为 O(nlogn)
O(n) + O(nlogn) 取最大时间复杂度,这种解决方案最终时间复杂度为 O(nlogn)。
有没有 时间复杂度为 O(n) 的方案呢?有,题中没有要求原地排序,所以可以使用创建新数组配合:
数组 nums 其实是有序的, 只不过负数平方之后可能成为最大数了,那么数组平方的最大值就在数组的两端,不是最左边就是最右边,不可能是中间,那可以使用头尾指针选择出最大数,插入到新数组末尾。
class Solution {
public int[] sortedSquares(int[] nums) {
int head_index = 0;
int tail_index = nums.length-1;
int[] new_nums = new int[nums.length];
// 从尾到头插入元素的平方
for(int new_nums_index = new_nums.length - 1; new_nums_index>=0; new_nums_index--){
int head_num_square = nums[head_index] * nums[head_index];
int tail_num_square = nums[tail_index] * nums[tail_index];
// 头指针指向的元素比较大
if( head_num_square >= tail_num_square ){
new_nums[new_nums_index] = head_num_square;
head_index++;
}else{
new_nums[new_nums_index] = tail_num_square;
tail_index--;
}
}
return new_nums;
}
}
合并两个有序数组
给你两个按 非递减顺序 排列的整数数组
nums1和nums2,另有两个整数m和n,分别表示nums1和nums2中的元素数目。请你 合并
nums2到nums1中,使合并后的数组同样按 非递减顺序 排列。注意: 最终,合并后数组不应由函数返回,而是存储在数组
nums1中。为了应对这种情况,nums1的初始长度为m + n,其中前m个元素表示应合并的元素,后n个元素为0,应忽略。nums2的长度为n。
这道题也可以使用两个指针,只不过和 有序数组的平方 的区别就是,两个指针不再指向数组的头尾,而是分别指向两个数组的开头:
class Solution {
public void merge(int[] nums1, int m, int[] nums2, int n) {
int nums1_index = 0;
int nums2_index = 0;
int[] new_nums = new int[m+n];
for(int new_nums_index=0;new_nums_index<new_nums.length;new_nums_index++){
// nums1 遍历完了,nums2 还有剩余
if(nums1_index >= m && nums2_index < n){
new_nums[new_nums_index] = nums2[nums2_index++];
continue;
}
// nums2 遍历完了,nums1 还有剩余
if(nums2_index >= n && nums1_index < m){
new_nums[new_nums_index] = nums1[nums1_index++];
continue;
}
// nums1 指针指向的元素比较小
if(nums1[nums1_index] <= nums2[nums2_index]){
new_nums[new_nums_index] = nums1[nums1_index++];
}else{
new_nums[new_nums_index] = nums2[nums2_index++];
}
}
// 因为题目要求要 合并 nums2 到 nums1 中,这里将新创建的数组复制到 nums1
for(int i=0;i<m+n;i++){
nums1[i] = new_nums[i];
}
}
}
反转字符串
编写一个函数,其作用是将输入的字符串反转过来。输入字符串以字符数组
s的形式给出。不要给另外的数组分配额外的空间,你必须 [原地] 修改输入数组、使用 O(1) 的额外空间解决这一问题。
看到这一题,第一反应,创建一个新数组,从尾到头遍历原数组,将值一个个插入到新数组中,但这道题需要原地解决,所以可以使用头尾指针,头尾指针指向的元素不是比较了,而是相互交换:
class Solution {
public void reverseString(char[] s) {
int head_index = 0;
int tail_index = s.length-1;
while(head_index < tail_index){
char tmp = s[head_index];
s[head_index] = s[tail_index];
s[tail_index] = tmp;
head_index++;
tail_index--;
}
}
}
注意,这里的结束条件是 head_index < tail_index,也就是当head_index >= tail_index时,循环就会被停止,这时 [0,head_index) 和 (tail_index,size-1] 区间的元素已经处理完,如果有 head_index = tail_index = index 的情况,那中间 index 的元素无需处理。
反转字符串II
给定一个字符串
s和一个整数k,从字符串开头算起,每计数至2k个字符,就反转这2k字符中的前k个字符。
- 如果剩余字符少于
k个,则将剩余字符全部反转。- 如果剩余字符小于
2k但大于或等于k个,则反转前k个字符,其余字符保持原样。
这道题的核心是反转每个下标从 2k 的倍数开始的,长度为 k 的子串。也就是有选择的使用 反转字符串 中的算法:
class Solution {
public String reverseStr(String s, int k) {
char[] cs = s.toCharArray();
int size = s.length();
for(int i=0 ; i<size; i=i+2*k){
int head_index = i;
int tail_index = i+k > size ? size-1 :i+k-1 ;
reverseString(cs,head_index,tail_index);
}
return String.valueOf(cs);
}
public void reverseString(char[] s,int head_index,int tail_index) {
while(head_index < tail_index){
char tmp = s[head_index];
s[head_index++] = s[tail_index];
s[tail_index--] = tmp;
}
}
}
注意上述代码,对 head_index 和 tail_index 的选择。
反转字符串中的单词 III
给定一个字符串
s,你需要反转字符串中每个单词的字符顺序,同时仍保留空格和单词的初始顺序。输入: s = "Let's take LeetCode contest"
输出: "s'teL ekat edoCteeL tsetnoc"
这个也是有选择性的使用 反转字符串 中的算法,只是头尾指针选择的不同:
class Solution {
public String reverseWords(String s) {
char[] cs = s.toCharArray();
int head_index = 0;
int end_index = 0;
for(int i=0;i< cs.length;i++){
// 寻找单词的结尾处
while (i < cs.length && cs[i] != ' ') {
i++;
}
end_index = i-1;
reverseString(cs,head_index,end_index);
head_index= i+1; // 下一个单词的开头处
}
return String.valueOf(cs);
}
public void reverseString(char[] s,int head_index,int tail_index) {
while(head_index < tail_index){
char tmp = s[head_index];
s[head_index++] = s[tail_index];
s[tail_index--] = tmp;
}
}
}
右旋字符串
字符串的右旋转操作是把字符串尾部的若干个字符转移到字符串的前面。
给定一个字符串 s 和一个正整数 k,请编写一个函数,将字符串中的后面 k 个字符移到字符串的前面,实现字符串的右旋转操作。
例如,对于输入字符串 "abcdefg" 和整数 2,函数应该将其转换为 "fgabcde"。
我们已经知道了反转字符串是使用头尾指针进行元素交换,反转字符串II 和 反转字符串 III 仅仅是选择合适的头尾指针调用反转字符串。
这道题看似不能使用 反转字符串 的逻辑,毕竟反转不再是以 字符 为单位。
可以换个角度分析:
⨳ 原始字符串 "abcdefg" 先来移除整体反转变成了 "gfedcba"
⨳ gfedcba 再将 gf 部分进行反转;再将 edcba 部分进行反转不就可以保证字符的顺序了嘛
class Solution {
public String rightReverseStr(String s,int k) {
char[] cs = s.toCharArray();
reverseString(cs, 0, cs.length - 1); //反转整个字符串
reverseString(cs, 0, k - 1); //反转前一段字符串
reverseString(cs, k, cs.length - 1); //反转后一段字符串
return String.valueOf(cs);
}
public void reverseString(char[] s,int head_index,int tail_index) {
while(head_index < tail_index){
char tmp = s[head_index];
s[head_index++] = s[tail_index];
s[tail_index--] = tmp;
}
}
}