977. 有序数组的平方
1. 题目
给你一个按 非递减顺序 排序的整数数组 nums,返回 每个数字的平方 组成的新数组,要求也按 非递减顺序 排序
输入:nums = [-4,-1,0,3,10]
输出:[0,1,9,16,100]
解释:平方后,数组变为 [16,1,0,9,100]
排序后,数组变为 [0,1,9,16,100]
2. 代码
#include <stdio.h>
#include <math.h>
#include <vector>
using namespace std;
class Solution {
public:
vector<int> sortedSquares(vector<int>& nums) {
int n = nums.size();
vector<int> ans(n);
int left = 0;
int right = n - 1;
int pos = n - 1;
while (left <= right) {
if (pow(nums[left],2) > pow(nums[right],2)) {
ans[pos] = pow(nums[left],2);
left++;
} else {
ans[pos] = pow(nums[right],2);
right--;
}
pos--;
}
return ans;
}
};
时间复杂度O(n), 空间复杂度O(1)
3. 题解
- 时间复杂度为 O(n),可得循环体中执行次数为n次,直接循环比较数字的平方,按次序放入, 而不是先平方,后排序
- 考虑采用对撞指针,即两个指针分别从数组开头和末尾开始,当left > right 说明数据比较完成,退出循环
- 想法是循环找出最大值,依次倒序放入新的vector, 或者循环找出最小值, 依次放入新的vector
考虑数组可能有正数,有负数,那么找出最小值可能是最左边值或者中间值,若是中间值,就不适用于对撞指针,故排除该方案
找出最大值,最大值只可能是最左边或最右边,所以采用该方案 - 有序数组,若nums[left]^2 > nums[right]^2, 说明left是负数, right是正数, 此时nums[left]^2应为最大值
- 有序数组,若nums[left]^2 < nums[right]^2, 说明left,right是正数, 此时nums[right]^2应为最大值
若nums[left]^2 = nums[right]^2, left , right可能相等,也可能互为相反数,此时nums[right]^2, nums[left]^2都是最大值 - 为什么不新增一个比较nums[left]^2 == nums[right]^2, 因为没有必要,仔细看,下一个循环就会将left++ 与 right比较, left+1 会插入就是第二大的平方值,所以没必要多比较一次相等的情况。
189. 轮转数组
1. 题目
给你一个数组,将数组中的元素向右轮转 k 个位置,其中 k 是非负数。
输入: nums = [1,2,3,4,5,6,7], k = 3
输出: [5,6,7,1,2,3,4]
解释:
向右轮转 1 步: [7,1,2,3,4,5,6]
向右轮转 2 步: [6,7,1,2,3,4,5]
向右轮转 3 步: [5,6,7,1,2,3,4]
2. 代码
class Solution {
public:
void rotate(vector<int>& nums, int k) {
int pos = k%(nums.size());
exchange(nums, 0, nums.size() - 1);
exchange(nums, 0, pos - 1);
exchange(nums, pos, nums.size() - 1);
}
void exchange(vector<int>& nums, int start, int end) {
while (start < end) {
int temp = nums[end];
nums[end] = nums[start];
nums[start] = temp;
// std::swap(nums[start], nums[end]);
start++;
end--;
}
}
};
3. 题解
- 看到题目首先想到将nums[i]赋值给nums[(i + k) % nums.size()] ,这种实现就需要新建一个数组用来存放赋值后的数据,再将新数组赋值原数组, 如下
void rotate(vector<int>& nums, int k) {
int n = nums.size();
vector<int> vet(n);
for (int i = 0; i < n; ++i) {
vet[(i + k) % n] = nums[i];
}
nums.assign(newArr.begin(), newArr.end());
}
此方法new了一个新的vector, 空间复杂度为O(n),要想节约空间,就需要尽可能再原数组上修改,而不是new新数组再赋值
- 在原数组修改,就需要数据交换
可以先翻转数组 7 6 5 4 3 2 1
发现要想达到最终结果, 只需将[0,k-1] 翻转, [k, nums.size()-1]翻转即可 - 注意第二次翻转和第三次翻转的中界位置,如果直接使用k的值,当k > nums.size()时,k -1 > nums.size()-1,这样在调用exchange置换时就会发生数组越界, 所以int pos = k%(nums.size());防止数组越界
283. 移动零
1. 题目
给定一个数组 nums,编写一个函数将所有 0 移动到数组的末尾,同时保持非零元素的相对顺序。
输入: [0,1,0,3,12]
输出: [1,3,12,0,0]
2. 代码
class Solution {
public:
void moveZeroes(vector<int>& nums) {
int left = 0;
int right = 0;
while (right < nums.size()) {
if (nums[right]) {
std::swap(nums[left], nums[right]);
left++;
}
right++;
}
}
};
3. 题解
- 采用双指针,左指针指向当前已经处理好的序列的尾部,右指针指向待处理序列的头部
右指针不断向右移动,每次右指针指向非零数,则将左右指针对应的数交换,同时左指针右移 - 因为right非0才换置换,所以left自第一次交换后不可能为0
167. 两数之和 II - 输入有序数组
1. 题目
给定一个已按照 非递减顺序排列 的整数数组 numbers ,请你从数组中找出两个数满足相加之和等于目标数 target 。
函数应该以长度为 2 的整数数组的形式返回这两个数的下标值。numbers 的下标 从 1 开始计数 ,所以答案数组应当满足 1 <= answer[0] < answer[1] <= numbers.length 。
你可以假设每个输入 只对应唯一的答案 ,而且你 不可以 重复使用相同的元素。
输入:numbers = [2,7,11,15], target = 9
输出:[1,2]
解释:2 与 7 之和等于目标数 9 。因此 index1 = 1, index2 = 2
2. 代码
class Solution {
public:
vector twoSum(vector& numbers, int target) {
vector vet(2);
int left = 0;
int right = numbers.size() - 1;
while (left < right) {
if (numbers[left] + numbers[right] == target) {
vet[0] = left + 1;
vet[1] = right + 1;
return vet;
} else if (numbers[left] + numbers[right] < target) {
left++;
} else if (numbers[left] + numbers[right] > target) {
right–;
}
}
return vet;
}
};
3. 题解
- 两两相加,和等于target,首先考虑双指针法
- 若相邻两两相加,如何保证每一种组合都被遍历呢,数据有序,何不使用对撞指针,两两相加, 若两数之和==target则return, 两数之和<target,那么left++, 两数之和 > target; right++
- while 退出条件应该是找到两数之和==target, 若找不到,则需要两个指针重复时退出
344. 反转字符串
1. 题目
编写一个函数,其作用是将输入的字符串反转过来。输入字符串以字符数组 s 的形式给出。
不要给另外的数组分配额外的空间,你必须原地修改输入数组、使用 O(1) 的额外空间解决这一问题。
输入:s = ["h","e","l","l","o"]
输出:["o","l","l","e","h"]
2. 代码
class Solution {
public:
void reverseString(vector<char>& s) {
int left = 0;
int right = s.size() - 1;
while (left < right) {
// char temp = s[right];
// s[right] = s[left];
// s[left] = temp;
std::swap(s[left], s[right]);
left++;
right--;
}
}
};
3. 题解
对撞指针反转字符串
557. 反转字符串中的单词 III
1. 题目
给定一个字符串,你需要反转字符串中每个单词的字符顺序,同时仍保留空格和单词的初始顺序。
输入:"Let's take LeetCode contest"
输出:"s'teL ekat edoCteeL tsetnoc"
2. 代码
class Solution {
public:
string reverseWords(string s) {
int pos = 0;
int n = s.length();
while (pos < n) {
int start = pos;
while (pos < n && s[pos] != ' ') {
pos++;
}
int end = pos-1;
reverseStr(s, start, end);
while (pos < n && s[pos] == ' ') {
pos++;
}
}
return s;
}
void reverseStr(string& s, int start, int end) {
while (start < end) {
std::swap(s[start], s[end]);
start++;
end--;
}
}
};
3. 题解
- 反转字符串中每个单词的字符顺序,首先考虑找到每个单词,再将单词翻转
- 是单词的条件:s[start] != ’ ’ && s[end] +1 == ’ ’
- 最后一个单词: s[start] != ’ ’ && s[end +1] < s.length()
- while (pos < n && s[pos] != ’ ') 找到第一个为‘ ’的字符,前一个是单词end
- while (pos < n && s[pos] == ’ ‘) 翻转前一个单词后, 找到之后第一个不为’ ’ 的字符即为后一个单词的start
876. 链表的中间结点
1. 题目
给定一个头结点为 head 的非空单链表,返回链表的中间结点。
如果有两个中间结点,则返回第二个中间结点。
输入:[1,2,3,4,5]
输出:此列表中的结点 3 (序列化形式:[3,4,5])
返回的结点值为 3 。 (测评系统对该结点序列化表述是 [3,4,5])。
2. 代码
struct ListNode {
int val;
ListNode *next;
ListNode() : val(0), next(nullptr) {}
ListNode(int x) : val(x), next(nullptr) {}
ListNode(int x, ListNode *next) : val(x), next(next) {}
};
ListNode* middleNode(ListNode* head) {
// 方法一: 链表无法获取下标,也无法计算长度,可转成数据存储,获取中间节点
// std::vector<ListNode*> vet;
// while (head != nullptr) {
// vet.push_back(head);
// head = head->next;
// }
// return vet[vet.size()/2];
// 方法二: 先计算链表长度,取链表长度/2的值
// int size = 0;
// ListNode* pos = head;
// while (pos != nullptr) {
// pos = pos->next;
// size++;
// }
// pos = head;
// int a = 0;
// while (a < size/2) {
// pos = pos->next;
// a++;
// }
// return pos;
// 方法三: 双指针法, 快慢指针同时出发,慢指针一次走一步, 快指针一次走两步,快指针到达链表末尾,慢指针则在中点
ListNode* slow = head;
ListNode* fast = head;
// 链表长度是双数: fast != nullptr
// 链表长度是单数: fast->next != nullptr
while (fast != nullptr && fast->next != nullptr) {
slow = slow->next;
fast = fast->next->next;
}
return slow;
}
void printList(ListNode* head)
{
ListNode* phead=head;
while(phead!=nullptr)
{
cout<<phead->val<<" ";
phead=phead->next;
}
cout<<"\n";
}
};
int main(int argc, char **argv)
{
ListNode* head = new ListNode(1); //不破坏头指针
ListNode* phead = head;
for(int i=2;i<=5;i++){
ListNode* node=new ListNode;
node->val=i;
node->next=nullptr;
phead->next=node;
phead=node;
}
cout<<"链表创建成功!\n";
Solution so;
so.printList(head); // 1 2 3 4 5
ListNode* list = so.middleNode(head);
so.printList(list); // 3 4 5
return 0;
}
3. 题解
见代码注释
19. 删除链表的倒数第 N 个结点
1. 题目
给你一个链表,删除链表的倒数第 n 个结点,并且返回链表的头结点
输入:head = [1,2,3,4,5], n = 2
输出:[1,2,3,5]
2. 代码
class Solution {
public:
ListNode* removeNthFromEnd(ListNode* head, int n) {
ListNode* tmp = new ListNode(0,head);
ListNode* slow = tmp;
ListNode* fast = head;
int i = 0;
while (i<n && fast != nullptr) {
fast = fast->next;
i++;
}
while (fast != nullptr) {
slow = slow->next;
fast = fast->next;
}
slow->next = slow->next->next;
ListNode* ans = tmp->next;
delete tmp;
return ans;
}
};
3. 题解
- 双指针法, 需要找到倒数第个结点的前一个结点,倒数第n个结点前一个依据倒数第一个结点计算, 倒数第n个结点比最后一个结点超前n;也就是间隔n-1个结点
- fast指向第n个结点,此时fast 和 slow一起走,直到fast到达链表末尾
- fast指向第n个结点,此时fast 和 slow一起走,直到fast到达链表末尾,slow恰好指向倒数第n个结点
- 删除结点获取目标结点的前一个结点比较方便,所以ListNode* tmp = new ListNode(0,head),其余操作不变,在删除时删除slow->next结点即可