代码随想录算法训练营21期

代码随想录算法训练营Day01|704二分查找,27移除元素

题目1:704二分查找

使用二分查找的前提条件:已排序的数组、无重复元素

思想:最关键的是理解区间的定义,是左闭右闭?还是左闭右开?两种区间的定义方式,对应的while循环条件是不一样的,对应的更新后的left,right的取值是不一样的。

题解1:区间定义为[left,right],左闭右闭

	int binarySearch1(vector<int> &nums,int target){
        int left = 0;
        int right = nums.size() - 1;
        while(left <= right){//当left==right,区间[left,right]依然有效,所以用<=
            int middle = left + ((right - left) / 2);//防止溢出,等同于(left+right)/2
            if(nums[middle] > target){
                right = middle - 1;//target在左区间,所以[left,middle-1]
            }else if(nums[middle] < target){
                left = middle + 1;//target在右区间,所以[middle+1,left]
            }else{
                return middle;
            }
        }
        //未找到目标值
        return -1;
    }

题解2:区间定义为[left,right),左闭右开

	int binarySearch2(vector<int> &nums,int target){
        int left = 0;
        int right = nums.size();//定义target在左闭右开的区间里,即:[left,right)
        while(left < right){//left==right是没有意义的
             int middle = left + ((right - left) / 2);
             if(nums[middle] > target){
                 right = middle;//target在左区间[left,middle)
             }else if(nums[middle] < target){
                 left = middle + 1;//target在右区间[middle+1,right)
             }else{
                 return middle;
             }
        }
        //未找到
        return -1;
    }

题目2:27.移除元素

解法1:暴力解法

class Solution{
public:
    int removeElement(vector<int> &nums,int val){
        int size = nums.size();
        for(int i = 0;i < size; ++i){
            if(nums[i] == val){
                /*这种方式不正确,因为nums[j+1]数组会越界*/                                                                
                /* for(int j = i;j < size;++j){ */
                /*     nums[j] = nums[j+1]; */
                /* } */
                for(int j = i+1;j < size;++j){
                    nums[j-1] = nums[j];
                }
                i--;//因为下标i以后的元素都向前移动了一位,所以i也向前移动一位
                size--;//此时数组的大小减1
            }   
        }       
        return size;
    }           
}; 

解法2:双指针法

双指针法(快慢指针法):通过一个快指针和慢指针在一个for循环下,完成两个for循环的工作。

快指针:寻找新数组的元素,新数组的元素就是不含有目标元素的数组。

慢指针:指向更新 新数组下标的位置。

	int removeElement2(vector<int> &nums,int val){
        int slowIndex = 0;
        for(int fastIndex = 0;fastIndex < (int)nums.size(); ++fastIndex){
            if(nums[fastIndex] != val){
                nums[slowIndex++] = nums[fastIndex];
            }
        }
        return slowIndex;
    }

题目3:35.搜索插入位置

题解1:暴力解法

	int searchInsert(vector<int>& nums,int target){
        //考虑以下三种情况
        //1.插入最开始的位置
        //2.若插入元素 和 数组中的某个元素相同
        //3.插入数组中,这是最平常的情况
        for(int i = 0;i < (int)nums.size();++i){
            if(nums[i] >= target){
                return i;
            }
        }
        //4.如果target是最大的,或者nums为空,则返回nums的长度
        return nums.size();
    }

题解2:二分查找

	int searchInsert_binary1(vector<int> &nums,int target){
        int left = 0;
        int right = nums.size() - 1;
        while(left <= right){
            int middle = left + ((right - left) / 2);
            if(nums[middle] > target){
                right = middle - 1;           
            }else if(nums[middle] < target){
                left = middle + 1;
            }else{
                return middle;
            }
        }
        return right + 1;
    }
	//左闭右开
    int searchInsert_binary2(vector<int> &nums,int target){
        int left = 0;
        int right = nums.size();
        while(left < right){
            int middle = left + ((right - left) / 2);
            if(nums[middle] > target){
                right = middle;
            }else if(nums[middle] < target){
                left = middle + 1;
            }else{
                return middle;
            }
        }
        return right;
    }

代码随想录算法训练营Day02

题目1:977有序数组的平方

解法1:暴力解法。

思想:把数组中的每一个元素都平法,然后再排序

vector<int> sortedSquares(vector<int> &nums){
    for(int i = 0;i < nums.size(); ++i){
        nums[i] *= nums[i]; 
    }
    sort(nums.begin(),nums.end());//快排
    return nums;
}

解法2:双指针法

思想:首先定义一个和原数组相同大小的数组,用来存储平方之后的元素。因为原数组中的元素有负数,所以平方之后的最大值只能在数组的两头产生而不可能在中间产生。所以想到双指针的方法。i指向第一个元素,j指向最后一个元素。要注意for循环退出的条件是什么

vector<int> sortedSquares2(vector<int> &nums){
    int k = nums.size() - 1;
    vector<int> result(nums.size(),0);

    for(int i=0,j=nums.size()-1;i <= j;){//注意这里要i <= j,因为最后要处理同一个元素,循环退出条件为i>j
        if(nums[i] * nums[i] < nums[j] * nums[j]){
            result[k--] = nums[j] * nums[j];
            j--;
        }else{
            result[k--] = nums[i] * nums[i];
            i++;
        }
    }
    return result;

}

题目2:209长度最小的子数组

解法1:暴力解法

//暴力解法:两个for循环,计算每个子串的和
int minSubArrayLen(int target,vector<int> &nums){
    int result = INT32_MAX;//最终结果
    int sum = 0;//子序列的数值之和
    int subLength = 0;//子序列的长度
    for(int i = 0;i < (int)nums.size();++i){//设置子序列起点为i
        sum = 0;
        for(int j = i;j < (int)nums.size();++j){//设置子序列终点为j
            sum += nums[j];
            if(sum >= target){//一旦发现子序列超过了target,则更新result
                subLength = j - i + 1;
                result = result < subLength ? result : subLength;
                break;//找到符合条件的子序列,但不一定是最短,所以break跳出内层循环,然后让i++
            }
        }
    }
    //如果result没有赋值的话,就返回0,说明没有符合条件的子序列
    return result == INT32_MAX ? 0 : result;
}

时间复杂度:O(n²)

解法2:滑动窗口

//解法2:滑动窗口,先确定终止位置再移动起始位置。
//用一个for循环,j表示滑动窗口的
int minSubArrayLen2(int target,vector<int> &nums){
    int result = INT32_MAX;
    int subLength = 0;
    int sum = 0;
    int i = 0;
    for(int j = 0;j < (int)nums.size();++j){
        sum += nums[j];
        while(sum >= target){
            subLength = j - i + 1;
            result = result < subLength ? result : subLength;
            sum -= nums[i];
            i++;
        }
    }
    //如果result没有赋值,就返回0,说明没有符合条件的子序列
    return result == INT32_MAX ? 0 : result;
}

时间复杂度:O(n);不要认为for循环里面嵌套while循环就是n²,关键看对数据的操作次数,元素在进入滑动窗口操作一次,出滑动窗口操作一次,O(2*n),所以就是O(n)

题目3:59螺旋矩阵II

非常考验代码的掌控能力

vector<vector<int>> generateMatrix(int n){
    vector<vector<int>> res(n,vector<int>(n,0));//使用vector定义一个n * n的二维数组
    int startX = 0,startY = 0;//定义每循环一圈的起始位置
    int loop = n / 2;//每个圈循环几次,例如n为奇数3,只是循环一圈,矩阵中间的值需要单独处理
    int mid = n / 2;//矩阵中间的位置,例如n为奇数3,中间的位置就是(1,1)
    int count = 1;//用来给矩阵中每一个空格赋值
    int offset = 1;//用来控制每一条边遍历的长度,每次循环右边界收缩1
    int i,j;
    
    while(loop--){
        i = startX;
        j = startY;

        //下面开始的四个for循环就是模拟转了一圈
        //从左到右
        for(j = startY; j < n - offset;j++){
            res[startX][j] = count++;
        }

        //从上到下
        for(i = startX;i < n - offset;i++){
            res[i][j] = count++;
        }

        //从右到左
        for(;j > startY;j--){
            res[i][j] = count++;
        }

        //从下到上
        for(;i > startX;i--){
            res[i][j] = count++;
        }

        //第二圈开始的时候,起始位置要各自加1。例如:第一圈起始位置(0,0),第二圈起始位置是(1,1)
        startX++;
        startY++;

        //offset控制每一圈里,每一条边遍历的长度
        offset++;

        //如果n为奇数的话,需要单独给矩阵最中间的位置赋值
        if(n % 2){
            res[mid][mid] = count;
        }
    }
    return res;
}

代码随想录算法训练营Day03

题目1:203移除链表元素

解法1:正常解法,分开处理头结点和中间结点

class Solution{
public:
    ListNode* removeElements(ListNode* head, int val) {
        // 删除头结点
        while (head != NULL && head->val == val) { // 注意这里不是if
            ListNode* tmp = head;
            head = head->next;
            delete tmp;
        }

        // 删除非头结点
        ListNode* cur = head;
        while (cur != NULL && cur->next!= NULL) {
            if (cur->next->val == val) {
                ListNode* tmp = cur->next;
                cur->next = cur->next->next;
                delete tmp;
            } else {
                cur = cur->next;
            }
        }
        return head;
    }
};

注意:Linux下写的怎么会有double free错误,还未解决。但是Leetcode网站上是AC的。

解法2:设置一个虚拟头结点再进行移除节点操作,这样子的话删除真正头结点的操作和非头结点的操作是一样的。

ListNode *removeElements(ListNode *head,int val){
	ListNode *dummyHead = new ListNode(0);
    dummyHead->next = head;
    ListNode *curr = dummyHead;
    while(curr->next != nullptr){
        if(curr->val == val){
        	ListNode *tmp = curr->next;
            curr->next = curr->next->next;
            delete tmp;
        }esle{
        	curr = curr->next;
        }
    }
    head = dummyHead;
	delete dummyHead;
	return head;
}

题目2:707设计链表

class MyLinkedList {
public:
    //定义链表节点结构体
    struct LinkedNode{
        int val;
        LinkedNode *next;
        LinkedNode(int val) : val(val),next(nullptr){}
        LinkedNode(int x,LinkedNode *next) : val(x),next(next){}
    };
    //初始化列表
    MyLinkedList() {
        _dummyHead = new LinkedNode(0);//这里定义的头结点是一个虚拟头结点,而不是链表真正的头结点
        _size = 0;
    }
    
    int get(int index) {
        if(index < 0 || index > (_size - 1)){
            return -1;
        }
        LinkedNode *curr = _dummyHead->next;
        while(index--){
            curr = curr->next;
        }
        return curr->val;
    }
    
    void addAtHead(int val) {
        LinkedNode *newNode = new LinkedNode(val);
        newNode->next= _dummyHead->next;
        _dummyHead->next = newNode;
        _size++;
    }
    
    void addAtTail(int val) {
        LinkedNode *newNode = new LinkedNode(val);
        LinkedNode *curr = _dummyHead;
        while(curr->next != nullptr){
            curr = curr->next;
        }
        curr->next = newNode;
        _size++;
    }
    
    void addAtIndex(int index, int val) {
        if(index < _size) return ;
        if(index > 0) index = 0;
        LinkedNode *newNode = new LinkedNode(val);
        LinkedNode *curr = _dummyHead;
        while(index--){
            curr = curr->next;
        }
        newNode->next = curr->next;
        curr->next = newNode;
        _size++;
    }
    
    void deleteAtIndex(int index) {
        if(index >= _size || index < 0){
            return ;
        }
        LinkedNode *curr = _dummyHead;
        while(index--){
            curr = curr->next;
        }
        LinkedNode *tmp = curr->next;
        curr->next = curr->next->next;
        delete tmp;
        tmp = nullptr;
        _size--;
    }

    //打印链表
    void printLinkedList() const{
        LinkedNode *curr = _dummyHead;
        while(curr->next != nullptr){
            cout << curr->next->val << ' ';
            curr = curr->next;
        }
        cout << endl;
    }

private:
    int _size;
    LinkedNode *_dummyHead;
};

题目3:206反转链表

思想:头插法。定义三个指针pre为null,curr指向头结点,temp执行curr->next目的是为了防止短链

ListNode *reverseList(ListNode *head){
    ListNode *temp;//保存curr的下一个节点
    ListNode *curr = head;//保存当前结点
    ListNode *pre = nullptr;
    while(curr != nullptr){
        temp = curr->next;
        curr->next = pre;
        //更新pre和curr
        pre = curr;
        curr = temp;
    }
    return pre;
}

代码随想录算法训练营Day04

题目1:24两两交换链表中的节点

思想:定义一个虚拟头结点,dummyHead->next=head。一定要画图理清楚指针的指向。

//两两交换相邻的节点
ListNode *swapPairs(ListNode *head){
    ListNode *dummyHead = new ListNode(0);
    dummyHead->next = head;//将虚拟头结点指向head,这样方便后面的删除操作
    ListNode *curr = dummyHead;
    while(curr->next != nullptr && curr->next->next != nullptr){
        ListNode *tmp1 = curr->next;//记录临时节点
        ListNode *tmp2 = curr->next->next->next;//记录临时结点

        //交换
        curr->next = curr->next->next;
        curr->next->next = tmp1;
        tmp1->next = tmp2;

        //curr移动两位,准备下一轮交换
        curr = curr->next->next;
    }
    return dummyHead->next; 
}

题目2:19删除链表的倒数第N个节点

思路:双指针法。定义一个虚拟结点,fast和slow初始化为dummyNode。先让fast移动n+1,然后再让slow和fast一起移动,移动到fast==nullptr为止,这样的话slow正好是指向倒数第n个节点的前一个节点。

ListNode *removeNthFromEnd(ListNode *head,int n){
    ListNode *dummyHead = new ListNode(0);
    dummyHead->next = head;
    ListNode *slow = dummyHead;
    ListNode *fast = dummyHead;
    while(n-- && fast != nullptr){
        fast = fast->next;
    } 
    //fast再提前走一步,因为需要让slow指向删除结点的上一个节点
    fast = fast->next;

    while(fast != nullptr){
        fast = fast->next;
        slow = slow->next;
    } 

    ListNode *tmp = slow->next;
    slow->next = slow->next->next;
    delete tmp;                                                                                                        

    return dummyHead->next;
}

题目3:面试题02.07.链表相交

思路:简单来说就是求两个链表交点的指针。特别注意:交点不是数值相等,而是指针相等。为了方便理解,假设节点元素的数值相等,则节点指针相等。

ListNode *getIntersectionNode(ListNode *headA,ListNode *headB){
    ListNode *currA = headA;
    ListNode *currB = headB;
    int lenA = 0,lenB = 0;
    while(currA != nullptr){
        lenA++;
        currA = currA->next;
    }
    while(currB != nullptr){
        lenB++;
        currB = currB->next;
    }

    currA = headA;
    currB = headB;
    //让currA为最长链表的头,lenA为其长度
    if(lenB > lenA){
        swap(lenA,lenB);
        swap(currA,currB);
    }
    //求长度差
    int gap = lenA - lenB;
    //让currA和currB在同一起点上(就是末尾位置对齐)
    while(gap--){
        currA = currA->next;
    }
    //遍历currA和currB,遇到相同则直接返回
    while(currA != nullptr){
        if(currA == currB){
            return currA;
        }
        currA = currA->next;
        currB = currB->next;
    }
    return nullptr;
}

题目4:环形链表II

代码随想录算法训练营Day05

周日休息

代码随想录算法训练营Day06

题目1:242有效的字母异位词

思路:前提是假设s和t字符串只包含小写字母。那么定义一个大小为26的整形数组result,用来记录对应小写字母的个数。遍历s将存在的小写字母统计result[s[i] - 'a']++,然后在遍历t再将result--。如果result数组全部为0,那就说明s和t是异位词。

bool isAnagram(string s,string t){
    int record[26] = {0};
    for(int i = 0;i < s.size();++i){
        record[s[i] - 'a']++;
    }
    for(int i = 0;i < t.size();++i){
        record[t[i] - 'a']--;
    }

    for(int i = 0;i < 26;i++){
        if(record[i] != 0){
            return  false;
        }
    }
    return true;
}

题目2:349两个数组的交集

思路:主要是unordered_set的使用,nums1和nums2,首先对nums1存到set中,再定义一个结果set,用来存放两个数组的交集。

vector<int> intersection(vector<int>& nums1, vector<int>& nums2) {
    unordered_set<int> result_set; // 存放结果,之所以用set是为了给结果集去重
    unordered_set<int> nums_set(nums1.begin(), nums1.end());
    for (int num : nums2) {
        // 发现nums2的元素 在nums_set里又出现过
        if (nums_set.find(num) != nums_set.end()) {
            result_set.insert(num);
        }
    }
    return vector<int>(result_set.begin(), result_set.end());
}

题目3:202快乐数

思路:1.如何求取n各个位。2.理解快乐数的定义,如何理解sum进入无限循环,在代码中如何体现?用一个集合,sum如果曾经出现过,那么就是说明已经进入了无限循环。

//取数值各个位上的单数之和
int getSum(int n){
    int sum = 0;
    while(n){
        sum += (n % 10) * (n % 10);
        n = n / 10;
    }
    return sum;
}
//判断是否是快乐数
bool isHappy(int n){
    unordered_set<int> set;
    while(1){
        int sum = getSum(n);
        if(sum == 1){
            return true;
        }
        //如果这个sum曾经出现过,说明已经进入了无限循环,立刻return false
        if(set.find(sum) != set.end()){
            return false;
        }else{
            set.insert(sum);
        }
        n = sum;
    }
}

题目4:两数之和

思路:定义一个map,用来存储元素和该元素对应的下边。遍历nums数组,在map中寻找与当前元素匹配的元素,如果没有找到那就将key,value加入到map中。

vector<int> twoSum(vector<int> &nums,int target){
    unordered_map<int,int> map;
    for(int i = 0;i < nums.size();++i){
        //遍历当前元素,并在map中寻找是否有匹配的key
        auto it = map.find(target - nums[i]);
        if(it != map.end()){
            return {it->second,i};
        }

        //如果没有找到匹配对,就把访问过的元素和下标加入到map中
        map.insert(pair<int,int>(nums[i],i));
    }
    return {};
}

代码随想录算法训练营Day07

题目1:454四数相加II

思路:

int fourSumCount(vector<int> &A,vector<int> &B,vector<int> &C,vector<int> &D){
    unordered_map<int,int> umap;//key:a+b的数值,value:a+b数值出现的次数
    //遍历A和B两个数组,统计两个数组元素之和,和出现的次数,放到map中
    for(int &a : A){
        for(int &b : B){
            umap[a+b]++;
        }
    }

    int count = 0;//统计a+b+c+d=0出现的次数
    //遍历C和D数组,找到0-(c+d)在map中出现过的话,就把map中key对应的value也就是出现的次数统计出来
    for(int &c : C){
        for(int &d :D){
            if(umap.find(0-(c+d)) != umap.end()){
                count += umap[0 - (c+d)];
            }
        }
    }
    return count;
}

题目2:383赎金信

思路:哈希解法

//哈希解法:
bool canConstruct2(string ransomNote,string magazine){
    int record[26] = {0};
    if(ransomNote.size() > magazine.size()){
        return false;
    }

    for(int i = 0;i < magazine.size();++i){
        //记录magazine中各个字符出现的次数
        record[magazine[i] - 'a']++;
    }
    
    for(int j = 0;j < ransomNote.size();++j){
        //遍历ransomNote,在record里对应的字符个数做--操作
        record[ransomNote[j] - 'a']--;
        if(record[ransomNote[j] - 'a'] < 0){
            return false;
        }
    }
    return true;
}

题目3:15三数之和

思路:双指针法。

vector<vector<int>> threeSum(vector<int> &nums){
    vector<vector<int>> result;
    sort(nums.begin(),nums.end());
    //找出a+b+c = 0
    //a = nums[i],b = nums[left],c = nums[right]
    for(int i = 0;i < nums.size();++i){
        //排序之后如果第一个元素已经大于0,那么无论如何组合都不可能组成三元组,直接返回结果就可以了
        if(nums[i] > 0){
            return result;
        }
        //去重a
        if(i > 0 && nums[i] == nums[i-1]){
            continue;
        }

        int left = i + 1;
        int right = nums.size()-1;
        while(right > left){
            if(nums[i] + nums[left] + nums[right] > 0) right--;
            else if(nums[i] + nums[left] + nums[right] < 0) left++;
            else{
                result.push_back({nums[i],nums[left],nums[right]});
                //去重逻辑应该放在找到一个三元组之后,对b 和c去重
                while(right > left && nums[right] == nums[right-1]) right--;
                while(right > left && nums[left] == nums[left+1]) left++;

                //找到答案时,双指针同时收缩
                right--;
                left++;
            }
        }
    }
    return result;
}

题目4:18四数之和

代码随想录算法训练营Day08

题目1:344反转字符串

思路:就是相当于字符数组的反转,就是前后交换

void reverseString(vector<char> &s){
    for(int i = 0,j = s.size()-1;i < s.size()/2;++i,--j){
        swap(s[i],s[j]);
    }
}

题目2:541反转字符串II

思路:关键是i的移动,还有是将题目描述的规则写出来

1.将每隔2k个字符的前k个字符进行反转。

2.剩余字符小于2k,但大于等于k个,则反转前k个字符。

3.剩余字符少于k个,则将剩余字符全部反转。

//反转函数
void reverse(string &s,int start,int end){
    for(int i = start,j = end;i < j;i++,j--){
        swap(s[i],s[j]);
    }
}
//实现题目中描述的规则
string reverseStr(string s,int k){
    for(int i = 0;i < s.size(); i += (2*k)){
        //1.每隔2k个字符的 前k个字符进行反转
        //2.剩余字符小于2k 但大于或等于 k 个,则反转前k个字符
        if(i + k <= s.size()){
            reverse(s,i,i+k-1);
            continue;
        }
        //3.剩余字符少于k个,则将剩余字符全部反转
        reverse(s,i,s.size() - 1);
    }
    return s;
}

题目3:剑指offer05替换空格

思路:先统计空格的个数,然后重新设置string的大小。两个指针一个指针指向字符串的头,另一个指针指向扩展后字符串的尾,然后从后向前遍历,如果遇到非空格那么直接复制,否则的话就进行替换。

string replaceSpace(string &s){
    int count = 0;//统计空格的个数
    int sOldSize = s.size();
    for(int i = 0;i < s.size();++i){
        if(s[i] == ' '){
            count++;
        }
    }

    //扩充字符串s的大小,也就是每个空格替换成%20之后的大小
    s.resize(s.size() + count*2);
    int sNewSize = s.size();
    //从后往前将空格替换为%20
    for(int i = sNewSize-1,j = sOldSize-1;j < i;i--,j--){
        if(s[j] != ' '){
            s[i] = s[j];
        }else{
            s[i] = '0';
            s[i-1] = '2';
            s[i-2] = '%';
            i -= 2;
        }
    }
    return s;
}

题目4:151反转字符串里的单词

思路:以空格为分隔符,将字符串读取到字符串输入流中,让后输出到变量word中,并将word存储在vector中,这样就把每个单词存储下来了。然后从后向前遍历vector,进行字符串拼接。

string reverseWords(string &s){
    istringstream ss(s);
    string word;
    vector<string> words;

    //以空格为分隔符分割字符串,将单词保存到向量中
    while(ss >> word){
        words.push_back(word);
    }

    string result;
    //从后往前遍历vector,逐个添加单词到结果字符串中
    for(int i = words.size()-1;i >= 0;--i){
        result += words[i];
        if(i > 0){
            result += " ";
        }
    }

    return result;
}

题目5:剑指offer58左旋转字符串

思路:先反转前n个字符,再反转第n个到字符串末尾的字符,然后在从头到尾全部反转。

string reverseLeftWords(string s,int n){
    reverse(s.begin(),s.begin()+n);
    reverse(s.begin()+n,s.end());
    reverse(s.begin(),s.end());
    return s;
}

代码随想录算法训练营Day09

KMP算法:还未掌握,需要看考研的那一块。理解前缀表,为什么要用前缀表,理解next数组。

题目1:28实现strStr()

题目2:459重复的子字符串

代码随想录算法训练营Day10

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值