Authur Whywait 做一块努力吸收知识的海绵
想看博主的其他所有leetcode卡片学习笔记链接?传送门点这儿文章目录
- 《数组和字符串》卡片- 小结
- 1. 旋转数组
- 暴力法
- 反转
- 利用最大公约数
- 使用环状替代
- 使用辅助数组
- 2. 杨辉三角II
- 普通解法
- 优化解法
- 3. 翻转字符串里的单词
- 法一:辅助(返回)字符串+反转
- 法二:反转-额外空间复杂度O(1)
- 4. 反转字符串中的单词III
- 5. 删除排序数组中的重复项
- 法一:暴力双指针法
- 法二:优化(快慢指针法)
- 6. 移动零
- 快慢指针法
给定一个数组,将数组中的元素向右移动 k 个位置,其中 k 是非负数。
输入: [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]
void rotate(int* nums, int numsSize, int k){
if(numsSize<1 || k<1 || k%numsSize==0) return;
for(int i=0; i<k%numsSize; i++){
int temp = nums[numsSize-1];
for(int j=numsSize-1; j>0; j--) nums[j] = nums[j-1];
nums[0] = temp;
}
return;
}
/*反转函数*/
void reverse(int* nums, int numsSize, int a, int b){
int low=a, high=b;
while(low<high){
int temp=nums[low];
nums[low] = nums[high];
nums[high] = temp;
high--; low ++;
}
return;
}
/*rotate函数主体*/
void rotate(int* nums, int numsSize, int k){
if(numsSize<1 || k<1 || k%numsSize==0) return;
int k0=k%numsSize;
reverse(nums, numsSize, 0, numsSize-1);
reverse(nums, numsSize, 0, k0-1);
reverse(nums, numsSize, k0, numsSize-1);
return;
}
利用 最大公约数 轻松求解
使用环状替代
O(n)算法,使用辅助数组,不符合题目空间复杂度O(1)的算法要求(虽然官方也给出了这种算法)
给定一个非负索引 k,其中 k ≤ 33,返回杨辉三角的第 k 行。
输入: 3
输出: [1,3,3,1]
int* getRow(int rowIndex, int* returnSize){
* returnSize = rowIndex + 1;
int** tri = (int **) malloc (sizeof(int*) * (rowIndex+1));
for(int i=0; i<rowIndex+1; i++){
tri[i] = (int *) malloc (sizeof(int) * (i+1));
tri[i][0] = 1;
tri[i][i] = 1;
}
for(int i=2; i<rowIndex+1; i++){
for(int j=1; j<i; j++){
tri[i][j] = tri[i-1][j-1] + tri[i-1][j];
}
}
return tri[rowIndex];
}
降低解法的空间复杂度
只申请一行的空间,从后往前操作就不需要考虑被覆盖的问题。
int* getRow(int rowIndex, int* returnSize){
* returnSize = rowIndex + 1;
int* array = (int *)malloc(sizeof(int) * (rowIndex+1));
for(int i=0; i<rowIndex+1; i++){
array[i]=1;
for(int j=i-1; j>0; j--) array[j] = array[j] + array[j-1];
array[0] = 1;
}
return array;
}
给定一个字符串,逐个翻转字符串中的每个单词。
输入: “the sky is blue”
输出: “blue is sky the”
输入: " hello world! "
输出: “world! hello”
解释:输入字符串可以在前面或者后面包含多余的空格,但是反转后的字符不能包括。
输入: “a good example”
输出: “example good a”
解释: 如果两个单词间有多余的空格,将反转后单词间的空格减少到只含一个。
思路:
1.除了多余空格,其他字符依次转移到辅助字符串里。
2.反转整个字符串,再把单词依次反转。
思路:
1.遍历字符串,删去多余空格,同时反转单词(使用双指针head
,tail
)。
2.最后反转整个字符串。
代码:
void reverse(char * s, int a, int b){
while(a<b){
char temp=s[a];
s[a++] = s[b];
s[b--] = temp;
}
return;
}
char * reverseWords(char * s){
int len=strlen(s), head=0, tail=0;
/*删除前面多余的空格*/
while(s[head]==' ') head++;
for(int i=0; i<len-head; i++) s[i] = s[i+head];
s[len-head] = '\0';
len -= head;head = 0;
int len1 = len;bool flag;
if(!len1) return "";
/*删除中间多余的空格并反转单词(如果最后有空格的话仍有一个空格未处理)*/
for(int i=0; i<len+1; i++){
printf("%d",i);
if(i>len1) break;
if(i==len1 && flag){//如果字符串末尾没有空格的情况
tail=i-1;
reverse(s, head, tail);
}
else if(s[i]!=' ' && s[i]!='\0'){
if(!flag) head=i; //若为不为空格且前面一个是空格:确定head
flag=true;
}
else if(s[i]==' '){
if(flag){
tail=i-1; //若为空格且前面一个不为空格:确定tail
reverse(s, head, tail);
flag = false;
}
else if(!flag){ //若为空格且前面一个也为空格,删除此空格且i--
len1--;
for(int j=i; j<len1; j++) s[j] = s[j+1];
s[len1] = '\0';
i--;
}else;
}else;
}
/*若有空格,处理字符串末尾空格*/
if(!flag){
len1--;
s[len1] = '\0';
}
/*反转整个字符串*/
reverse(s, 0, len1-1);
return s;
}
细心的读者就会发现了,为什么有一行输出
printf("%d",i);
呢?因为是直接在leetcode上调试,所以要输出一些东西便于调试。结果在删除的时候遇到了问题,删除这个printf会导致程序的执行结果发生变化,真的是非常amazing了。
最后就把带着printf("%d",i);
的代码提交了~
执行时间长确是一个硬伤。
给定一个字符串,你需要反转字符串中每个单词的字符顺序,同时仍保留空格和单词的初始顺序。
输入: “Let’s take LeetCode contest”
输出: “s’teL ekat edoCteeL tsetnoc”
【思路】
和翻转字符串里的单词相比就很简单了,思路相同,故直接上代码:
void reverse(char * s, int a, int b){
while(a<b){
char temp=s[a];
s[a++] = s[b];
s[b--] = temp;
}
return;
}
char * reverseWords(char * s){
int len=strlen(s), head=0, tail;
if(!len) return "";
bool flag=true;
for(int i=0; i<len; i++){
if(s[i]==' '){
if(flag){
tail = i-1;
reverse(s, head, tail);
}
flag = false;
}
else if(s[i]!=' '){
if(!flag) head = i;
flag = true;
}
}
reverse(s, head, len-1);
return s;
}
给定一个排序数组,你需要在 原地 删除重复出现的元素,使得每个元素只出现一次,返回移除后数组的新长度。
不要使用额外的数组空间,你必须在 原地 修改输入数组 并在使用 O(1) 额外空间的条件下完成。
给定数组 nums = [1,1,2],
函数应该返回新的长度 2, 并且原数组 nums 的前两个元素被修改为 1, 2。
你不需要考虑数组中超出新长度后面的元素。
思路:遇到重复,所有后面的元素往前移一个
int removeDuplicates(int* nums, int numsSize){
int len=numsSize;
for(int i=1; i<len; i++){
if(nums[i]==nums[i-1]){
len--;
for(int j=i; j<len; j++) nums[j] = nums[j+1];
i--;
}
}
return len;
}
法一中的遇到重复就把后面全体往前移一个位置,但是如果诸如011111111111112
之类的呢?就会多很多无谓的操作。
于是,快慢指针法就应运而生。
int removeDuplicates(int* nums, int numsSize){
if(!numsSize) return 0;
int slow=1, quick=1;
while(quick<numsSize){
if(nums[quick]!=nums[quick-1]) nums[slow++] = nums[quick++];
else quick++;
}
return slow;
}
很明显,不管是在执行用时还是在内存消耗方面,快慢指针法都远远优于方法一。
给定一个数组 nums
,编写一个函数将所有 0 移动到数组的末尾,同时保持非零元素的相对顺序。
输入: [0,1,0,3,12]
输出: [1,3,12,0,0]
直接用最优的快慢指针法
void moveZeroes(int* nums, int numsSize){
int num=0; int slow=0, quick=0;
while(quick<numsSize){
if(nums[quick]) nums[slow++] = nums[quick++];
else{
quick++;
num++;
}
}
for(int i=numsSize-num; i<numsSize; i++) nums[i]=0;
return;
}