目录
一、引入
先看一道例题 例题
给你一个按非递减顺序排序的整数数组 nums,返回 每个数字的平方组成的新数组,要求也按非递减顺序排序
。
示例 1:
输入:nums = [-4,-1,0,3,10]
输出:[0,1,9,16,100]
解释:平方后,数组变为 [16,1,0,9,100]
排序后,数组变为 [0,1,9,16,100]
示例 2:输入:nums = [-7,-3,2,3,11]
输出:[4,9,9,49,121]
提示:
1 <= nums.length <= 104
-104 <= nums[i] <= 104
nums 已按 非递减顺序 排序来源:力扣(LeetCode)
对于这道题,我们最先想到的方式是另开一个数组,将该数组的各个数的平方一一存入该数组,之后重新排序。
class Solution {
public:
vector<int> sortedSquares(vector<int>& nums) {
vector<int> num(nums.size()); //开辟新数组
for(int i = 0; i < nums.size(); ++i)//将原数组的平方处理后一一存入新数组
{
num[i] = nums[i] * nums[i];
}
sort(num.begin(), num.end(), less());//对新数组排序
return num;//返回新数组
}
};
对这个解法我们做点改动
class Solution {
public:
vector<int> sortedSquares(vector<int>& nums) {
// vector<int> num(nums.size());
// for(int i = 0; i < nums.size(); ++i)
// {
// num[i] = nums[i] * nums[i];
// }
// sort(num.begin(), num.end(), less());
// return num;
vector<int > num(nums.size());//新的空间
int i = 0, j = nums.size() - 1;
for(int k = nums.size() - 1; i <= j;)//遍历数组
{
if(nums[i] * nums[i] < nums[j] * nums[j])
{
num[k] = nums[j] * nums[j];
j--;
}
else
{
num[k] = nums[i] * nums[i];
i++;
}
k--;
}
return num;
}
};
在这个解法中,因为包含负数,所以平方数最大值可能出现在头与尾;因此在遍历过程中,逐步比较头一个与最后一个。
二、算法思想
双指针其实是对原有算法的一种优化方式,常见的有快慢指针,对撞指针。
三、例题
第一题
给定一个数组 nums,编写一个函数将所有 0 移动到数组的末尾,同时保持非零元素的相对顺序。
请注意 ,必须在不复制数组的情况下原地对数组进行操作。
示例 1:
输入: nums = [0,1,0,3,12]
输出: [1,3,12,0,0]
示例 2:输入: nums = [0]
输出: [0]提示:
1 <= nums.length <= 104
-231 <= nums[i] <= 231 - 1来源:力扣(LeetCode)
先一般性的解法
class Solution {
public:
void moveZeroes(vector<int>& nums) {
vector<int > num;
for(int i = 0, j = 0; i < nums.size(); ++i)//将nums中非0数排入num
{
if(nums[i] != 0)
{
num.push_back(nums[i]);
j++;
}
}
for(int i = 0, j = 0; i < nums.size(); ++i)//将nums中0排入num
{
if(nums[i] == 0)
{
num.push_back(nums[i]);
j++;
}
}
nums.assign(num.begin(), num.end());//将num复制进nums中
}
};
双指针解法
我们设想如果两个指针,一个指针在左边,一个在右边查找非0数,如果右边的数字不是0,就将右边的数字换到左边。
class Solution {
public:
void moveZeroes(vector<int>& nums) {
// vector<int > num;
// for(int i = 0, j = 0; i < nums.size(); ++i)
// {
// if(nums[i] != 0)
// {
// num.push_back(nums[i]);
// j++;
// }
// }
// for(int i = 0, j = 0; i < nums.size(); ++i)
// {
// if(nums[i] == 0)
// {
// num.push_back(nums[i]);
// j++;
// }
// }
// nums.assign(num.begin(), num.end());
int i = 0; //指向非0数
int j = 0; //
while(i < nums.size())//遍历整个数组
{
if (nums[i])//右边遇到非0数
{
swap(nums[i], nums[j]);
j++;
}
i++;
}
}
};
做个动画演示一个整个过程
第二题
编写一个函数,其作用是将输入的字符串反转过来。输入字符串以字符数组 s 的形式给出。
不要给另外的数组分配额外的空间,你必须原地修改输入数组、使用 O(1) 的额外空间解决这一问题。
示例 1:
输入:s = ["h","e","l","l","o"]
输出:["o","l","l","e","h"]
示例 2:输入:s = ["H","a","n","n","a","h"]
输出:["h","a","n","n","a","H"]
提示:
1 <= s.length <= 105
s[i] 都是 ASCII 码表中的可打印字符来源:力扣(LeetCode)
这题就不用一般性解法演示了,比较复杂,难度不大
定义两个指针,一个指向字符串的首位,一个指向字符串的末尾,逐步将首位与末尾交换
class Solution {
public:
void reverseString(vector<char>& s) {
int i = 0, j = s.size() - 1;
for(; i <= j; i++, j--)
{
swap(s[i], s[j]); //交换头与尾
}
}
};
第三题
给定一个头结点为 head 的非空单链表,返回链表的中间结点。
如果有两个中间结点,则返回第二个中间结点。
示例 1:
输入:[1,2,3,4,5]
输出:此列表中的结点 3 (序列化形式:[3,4,5])
返回的结点值为 3 。 (测评系统对该结点序列化表述是 [3,4,5])。
注意,我们返回了一个 ListNode 类型的对象 ans,这样:
ans.val = 3, ans.next.val = 4, ans.next.next.val = 5, 以及 ans.next.next.next = NULL.
示例 2:输入:[1,2,3,4,5,6]
输出:此列表中的结点 4 (序列化形式:[4,5,6])
由于该列表有两个中间结点,值分别为 3 和 4,我们返回第二个结点。
提示:
给定链表的结点数介于 1 和 100 之间。
来源:力扣(LeetCode)
对于这题我们很容易能想的方法就是直接找到中间的那个数,但是链表又无法直接定位,因此我们可以把该链表转换为数组,再定位到中间的数。
/**
* Definition for singly-linked list.
* 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) {}
* };
*/
class Solution {
public:
ListNode* middleNode(ListNode* head) {
vector <ListNode* > L = {head};
while (L.back()->next != NULL)
{
L.push_back(L.back()->next);
}
return L[L.size() / 2];
}
};
这个是把链表的数据转换到数组中,再直接输出中间节点。我们做点改进,在原有的链表中,找到中间的节点。
/**
* Definition for singly-linked list.
* 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) {}
* };
*/
class Solution {
public:
ListNode* middleNode(ListNode* head) {
/*
* 第一种
*/
// vector <ListNode* > L = {head};
// while (L.back()->next != NULL)
// {
// L.push_back(L.back()->next);
// }
// return L[L.size() / 2]
/*
* 第二种
*/
int n = 0;//初始化
ListNode* cur = head;
while(cur != nullptr)//确定节点数量
{
++n;
cur = cur -> next;
}
int i = 0;
cur = head;//初始化cur
while (i < n / 2)//找到中间节点
{
i++;
cur = cur->next;
}
return cur;
}
};
再用双指针的方式优化一下,这里用到的是快慢指针。
/**
* Definition for singly-linked list.
* 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) {}
* };
*/
class Solution {
public:
ListNode* middleNode(ListNode* head) {
/*
* 第一种
*/
// vector <ListNode* > L = {head};
// while (L.back()->next != NULL)
// {
// L.push_back(L.back()->next);
// }
// return L[L.size() / 2]
/*
* 第二种
*/
// int n = 0;
// ListNode* cur = head;
// while(cur != nullptr)//确定节点数量
// {
// ++n;
// cur = cur->next;
// }
// int i = 0;
// cur = head;//初始化cur
// while (i < n / 2)//找到中间节点
// {
// i++;
// cur = cur->next;
// }
// return cur;
/*
* 第三种
*/
ListNode* slow = head;
ListNode* fast = head;
while (fast != nullptr && fast->next != nullptr)
{
slow = slow->next;
fast = fast->next->next;
}
return slow;
}
};
快慢指针是利用两个指针的速度不一致,比如这题中,快指针的速度是慢指针的两倍,快指针到末尾的时候,恰好慢指针到达链表的中间。
第四题
给你一个数组,将数组中的元素向右轮转 k 个位置,其中 k 是非负数。
示例 1:
输入: 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:输入:nums = [-1,-100,3,99], k = 2
输出:[3,99,-1,-100]
解释:
向右轮转 1 步: [99,-1,-100,3]
向右轮转 2 步: [3,99,-1,-100]
提示:
1 <= nums.length <= 105
-231 <= nums[i] <= 231 - 1
0 <= k <= 105来源:力扣(LeetCode)
class Solution {
public:
void rotate(vector<int>& nums, int k) {
//int i = 0; int j = nums.zise() - k;
vector<int > num(nums.size());//开辟一个数组空间
int temp;
for(int i = 0; i < nums.size(); i++)
{
if(i < nums.size() - k)
{
num[(i + k) % nums.size()] = nums[i];//对nums.size()取模,防止溢出
}
else
{
temp = nums.size() - i - k;
temp = fabs(temp);
temp = temp % nums.size();//同样取模,防止溢出
num[temp] = nums[i];
}
}
nums.assign(num.begin(), num.end());
}
};
后面看官方解答,似乎并不需要分类,于是乎
class Solution {
public:
void rotate(vector<int>& nums, int k) {
//int i = 0; int j = nums.zise() - k;
vector<int > num(nums.size());//开辟一个数组空间
int temp;
for(int i = 0; i < nums.size(); i++)
{
// if(i < nums.size() - k)
// {
// num[(i + k) % nums.size()] = nums[i];//对nums.size()取模,防止溢出
// }
// else
// {
// temp = nums.size() - i - k;
// temp = fabs(temp);
// temp = temp % nums.size();//同样取模
// num[temp] = nums[i];
// }
num[(i + k) % nums.size()] = nums[i];
}
nums.assign(num.begin(), num.end());
}
};
这个也是可以AC的。
class Solution {
public:
void rotate(vector<int>& nums, int k) {
//int i = 0; int j = nums.zise() - k;
// vector<int > num(nums.size());//开辟一个数组空间
// int temp;
// for(int i = 0; i < nums.size(); i++)
// {
// // if(i < nums.size() - k)
// // {
// // num[(i + k) % nums.size()] = nums[i];//对nums.size()取模,防止溢出
// // }
// // else
// // {
// // temp = nums.size() - i - k;
// // temp = fabs(temp);
// // temp = temp % nums.size();//同样取模
// // num[temp] = nums[i];
// // }
// num[(i + k) % nums.size()] = nums[i];
// }
// nums.assign(num.begin(), num.end());
int i = 0, j = nums.size() - 1;
while(i <= j)//将所有的数调转顺序。
{
swap(nums[i], nums[j]);
i++;
j--;
}
i = 0;
j = k % nums.size() - 1;//取模,防止数据溢出
while(i <= j)//调转前数列的顺序
{
swap(nums[i], nums[j]);
i++;
j--;
}
i = k % nums.size();//取模,防止数据溢出
j = nums.size() - 1;
while(i <= j)//调转后序列的顺序
{
swap(nums[i], nums[j]);
i++;
j--;
}
}
};
用双指针的方法,我们可以这样,将整个数列翻转,之后以k为界,分别将前后数列翻转。
官方解答
class Solution {
public:
void reverse(vector<int>& nums, int start, int end) {
while (start < end) {
swap(nums[start], nums[end]);
start += 1;
end -= 1;
}
}
void rotate(vector<int>& nums, int k) {
k %= nums.size();
reverse(nums, 0, nums.size() - 1);
reverse(nums, 0, k - 1);
reverse(nums, k, nums.size() - 1);
}
};
第五题
给定一个字符串 s ,你需要反转字符串中每个单词的字符顺序,同时仍保留空格和单词的初始顺序。
示例 1:
输入:s = "Let's take LeetCode contest"
输出:"s'teL ekat edoCteeL tsetnoc"
示例 2:输入: s = "God Ding"
输出:"doG gniD"
提示:
1 <= s.length <= 5 * 104
s 包含可打印的 ASCII 字符。
s 不包含任何开头或结尾空格。
s 里 至少 有一个词。
s 中的所有单词都用一个空格隔开。来源:力扣(LeetCode)
在写这题的时候,我们想的就是,针对每一个单词分别调换。
class Solution {
public:
string reverseWords(string s) {
//string str;//新的空间,用于储存已经反转好的单词
int i;
int begin = 0, end = 0;//交换时的开始位置与结束位置
int sLength = s.length();//字符串长度
int count = 0;//定义遍历字母的数量
while(count < sLength) //count的值不可以随意的变动,它表示的是遍历的字母位置
{
begin = count;//将i初始化至当前位置
while(s[count] != ' ' && count < sLength)//确定j的位置,每个单词的结尾
{
count++;
}
end = count - 1;
while(begin <= end)//颠倒单词的字母顺序
{
swap(s[begin], s[end]);
begin++;
end--;
}
while(s[count] == ' ' && count < sLength)//定位到下一个单词
{
count++;
}
}
return s;
}
};