刷题记录4.17-5.6

刷题记录

27.移除元素

方法一:

class Solution {
public:
    int removeElement(vector<int>& nums, int val) {
        for(int i = 0; i < nums.size(); i++)
        {
            if(nums[i] == val)
            {
                for(int j = i; j < nums.size()-1; j++)
                {
                    nums[j] = nums[j+1];
                }
                nums.resize(nums.size()-1);
                i--;
            }
        }
        return nums.size();
    }
};

这是自己写出来的方法,意思是先枚举数组每一个元素,当这个元素==val时,就将后面的元素全部往前挪一个位置,这样的时间复杂度是On方的

方法二:(快慢指针)

class Solution {
public:
    int removeElement(vector<int>& nums, int val) {
        int i = 0, j = 0;
        while(j < nums.size())
        {
            if(nums[j] != val) nums[i++] = nums[j++];
            else j++;
        }
        return i;
    }
};

i是慢指针,j是快指针,当nums[j]不是val时就将nums[j]覆盖nums[i],这样就只使用了On的时间复杂度完成了这一题

977.有序数组的平方

方法一:

class Solution {
public:
    vector<int> sortedSquares(vector<int>& nums) {
        vector<int> res;
        for(int i = 0; i < nums.size(); i++)
        {
            res.push_back(nums[i]*nums[i]);
        }
        sort(res.begin(), res.end());
        return res;
    }
};

这个方法就是取巧使用了sort函数,先将所有元素的平方值存入数组中,最后使用sort函数将其升序排序。

方法二:(双指针)

class Solution {
public:
    vector<int> sortedSquares(vector<int>& nums) {
        vector<int> res(nums.size());
        int i = 0, j = nums.size()-1, n = nums.size();
        while(i <= j)
        {
            if(abs(nums[i]) < abs(nums[j]))
            {
                res[--n] = nums[j]*nums[j];
                j--;
            }
            else 
            {
                res[--n] = nums[i]*nums[i];
                i++;
            }
        }
        return res;
    }
};

考虑到给出的数组是有序的,那么左右两边的值取平方一定是放在新数组的末尾,所以这里我们就定义两个指针,一个在头,一个在尾,相向遍历数组,直到i下标大于j下标就退出循环。当然这里需要将新数组从后往前录入数据,所以需要事先开辟好res数组的空间。

209.长度最小的子数组

class Solution {
public:
    int minSubArrayLen(int target, vector<int>& nums) {
        int left = 0, sum = 0, ans = nums.size()+1;
        for(int right = 0; right < nums.size(); right++)
        {
            sum += nums[right];
            while(sum >= target)
            {
                ans = min(ans, right-left+1);
                sum -= nums[left++];
            }
        }
        return ans == nums.size()+1 ? 0 : ans;
    }
};

这道题运用的方法也是双指针滑动窗口,外层循环枚举数组每个元素,内层循环用来使符合的子数组长度最小,只要这段子数组的和一直>=target,左指针就不断的右移并更新ans长度和减去左边界的值。

707.设计链表

class MyLinkedList {
public:

    struct ListNode
    {
        int val;
        ListNode* next;
        ListNode(int num) : val(num), next(nullptr){}
    };

    MyLinkedList() {
        dummyhead = new ListNode(0);
        Size = 0;
    }
    
    int get(int index) {
        if(index+1 > Size || index < 0) return -1;
        ListNode* pmove = dummyhead;
        while(index--) pmove = pmove->next;
        return pmove->next->val;
    }
    
    void addAtHead(int val) {
        ListNode* s = new ListNode(val);
        s->next = dummyhead->next;
        dummyhead->next = s;
        Size++;
    }
    
    void addAtTail(int val) {
        ListNode* pmove = dummyhead;
        while(pmove->next) pmove = pmove->next;
        ListNode* s = new ListNode(val);
        pmove->next = s;
        Size++;
    }
    
    void addAtIndex(int index, int val) {
        if(index > Size) return;
        ListNode* pmove = dummyhead;
        while(index--) pmove = pmove->next;
        ListNode* s = new ListNode(val);
        s->next = pmove->next;
        pmove->next = s;
        Size++;
    }
    
    void deleteAtIndex(int index) {
        if(index >= Size || index < 0) return;
        ListNode* pmove = dummyhead;
        while(index--) pmove = pmove->next;
        pmove->next = pmove->next->next;
        Size--;
    }

private:
    int Size;
    ListNode* dummyhead;
};

/**
 * Your MyLinkedList object will be instantiated and called as such:
 * MyLinkedList* obj = new MyLinkedList();
 * int param_1 = obj->get(index);
 * obj->addAtHead(val);
 * obj->addAtTail(val);
 * obj->addAtIndex(index,val);
 * obj->deleteAtIndex(index);
 */

这个题让设计一个链表,包括链表的基本操纵,基本的插入删除操作还是没有忘,只不过在一些细节上不是很完美,首先定义一个结构体,因为是链表加上题目给的数据是int类型的,所以val是数据域,next是指针域。定义两个私有成员变量,大小size和虚拟头节点dummyhead。我感觉这个题ac不了的问题在于不能明确结构体结点和类之间的差别。

206.反转链表

双指针迭代法:

/**
 * 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* reverseList(ListNode* head) {
        ListNode* cur = head, *pre = nullptr, *temp;
        while(cur != nullptr)
        {
            temp = cur->next;
            cur->next = pre;
            pre = cur;
            cur = temp;
        }
        return pre;
    }
};

这里最好是将cur = head, pre = nullptr,因为第一个结点在反转完链表后要置为空的,如果是pre = head, cur = head->next的话,会导致代码变得复杂,需要单独将第一次操作提出来,还需要判断head是否为空。

递归法:

/**
 * 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* reverse(ListNode* cur, ListNode* pre)
    {
        if(cur == nullptr) return pre;
        ListNode* temp = cur->next;
        cur->next = pre;
        return reverse(temp, cur);
    }

    ListNode* reverseList(ListNode* head) {
        return reverse(head, nullptr);
    }
};

递归理解起来可能会比较晦涩,其实本质上也是双指针迭代,两个参数初始化cur=head, pre = nullptr,递归终点也是在cur==nullptr的时候退出,这里在写的时候都需要注意的是:需要借助一个临时变脸来记录cur->next,否则会导致死循环或者递归结果不正确,那是因为在第二次循环或递归之前会有cur->next=pre的操作。

24.两两交换链表中的节点

解法一:

/**
 * 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:

    void exchange(ListNode* node)
    {
        if(node == nullptr || node->next == nullptr) return;

        int temp = node->val;
        node->val = node->next->val;
        node->next->val = temp;

        exchange(node->next->next);
    }

    ListNode* swapPairs(ListNode* head) {
        //if(head == nullptr) return nullptr;
        exchange(head);
        return head;
    }
};

这也是我自己的解法,当然是在捡漏,因为每个节点的val值一定是相同的,所以我就只改动了数据域而非指针域,但是当数据域多的话用这个方法就显得十分笨拙。

解法二:

class Solution {
public:
    ListNode* swapPairs(ListNode* head) {
        ListNode* dummyhead = new ListNode(0, head);
        ListNode* cur = dummyhead;
        while(cur->next != nullptr && cur->next->next != nullptr)
        {
            ListNode* temp1 = cur->next;
            ListNode* temp2 = cur->next->next->next;
            cur->next = cur->next->next;
            cur->next->next = temp1;
            temp1->next = temp2;
            cur = cur->next->next;
        }
        return dummyhead->next;
    }
};

这个解法就是修改指针域,首先定义一个虚拟头节点dummyhead,假设节点a->b->c当cur指向a的时候才能将b和c的指针域进行交换,以此类推,如何判断循环退出的条件?当链表为奇数个数时:cur->next->next == nullptr是循环退出的标志,当链表个数为偶数个时:cur->next == nullptr为退出标志。注意cur->next一定要在cur->next->next的前面,否则会给出空指针异常。再就是更改指针的逻辑,一定要确定好更改的顺序,时常要用到临时的指针变量进行保存位置,否则连接就会混乱。工作指针移动的条件是cur = cur->next->next,忘写会时间超限,并且一定不能出错。最后返回虚拟头节点的next即为最后的答案。

19.删除链表的倒数第N个节点

解法一:

/**
 * 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* removeNthFromEnd(ListNode* head, int n) {
        ListNode* dummyhead = new ListNode(0, head);
        int num = 0;
        ListNode* pmove = head;
        while(pmove)
        {
            num++;
            pmove = pmove->next;
        }
        int pos = num-n;
        cout << pos;
        ListNode* p = dummyhead;
        int count = 0;
        while(p)
        {
            if(count == pos)
            {
                p->next = p->next->next;
                break;
            }
            count++;
            p = p->next;
        }
        return dummyhead->next;
    }
};

这当然也是最笨拙的方法,遍历两边链表。

解法二:

class Solution {
public:
    ListNode* removeNthFromEnd(ListNode* head, int n) {
        ListNode* dummyhead = new ListNode(0, head);
        ListNode* fast = dummyhead, *low = dummyhead;
        int count = n+1;
        while(count-- && fast) fast = fast->next;
        while(fast)
        {
            fast = fast->next;
            low = low->next;
        }
        low->next = low->next->next;
        return dummyhead->next;
    }
};

这里只遍历了一遍链表,果然在遇到链表题的时候双指针有时候真的有奇效啊。这里先用fast指针遍历前n+1个元素,然后再让fast指针和low指针一起移动,直到fast指针移到末尾,这时low指针就指向了要删除元素的前一个位置,最后low->next = low->next->next达到删除链表倒数第N个节点的效果。

142.环形链表II

解法一:

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode *detectCycle(ListNode *head) {
        unordered_set<ListNode*> visited;
        ListNode* pmove = head;
        while(pmove)
        {
            if(find(visited.begin(), visited.end(), pmove) != visited.end()) return pmove;
            visited.insert(pmove);
            pmove = pmove->next;
        }
        return nullptr;
    }
};

这道题用的是哈希表,唉我看到题的第一映像也是用哈希表,可是我就在想,如果遇到了重复的元素值那就哈希表就不能达到效果,那么为什么这个哈希表却能用呢?原来这个是将哈希表的类型定义成节点类型的指针,这就说得通了,虽然节点的val值可能会相同,但是每个节点所存储的地址一定是不同的,当遍历到哈希表有存储过的节点时,这个节点就是我们要找的环形链表的起始位置。

这个题告诉了我们什么?其实你的方法可能是正确的,你其实离真相很近,但是你却中途选择了放弃,当然这样你也与成功失之交臂了。

解法二:

class Solution {
public:
    ListNode *detectCycle(ListNode *head) {
        ListNode* fast = head, *low = head;
        while(true)
        {
            if(fast == nullptr || fast->next == nullptr) return nullptr;
            fast = fast->next->next;
            low = low->next;
            if(fast == low) break;
        }
        fast = head;
        while(fast != low)
        {
            fast = fast->next;
            low = low->next;
        }
        return fast;
    }
};

这个呢主要是用到了数学的关系式,这对于我这个数学菜鸡来说简直就是天方夜谭,主要步骤就是让fast指针每次走两个节点,low指针每次走一个节点,这样如果有环的话fast指针一定会追上low指针,然后再将fast指针置为head再次移动fast和low,此时两个指针都是一个节点一个节点地移动,直到fast和low相等的时候就是我们要找到的目标位置。

242.有效的字母异位词

class Solution {
public:
    //a = 97
    bool isAnagram(string s, string t) {
        vector<int> hash(26, 0);
        for(char w : s) hash[w-'a']++;
        for(char w : t) hash[w-'a']--;
        for(int i = 0; i < 26; i++)
        {
            if(hash[i] != 0) return false;
        }
        return true;
    }
};

这里使用的是哈希表来解决问题,哈希表分为三种:数组、set和map,数组的应用场景通常在能够确定数组大小等简单情景下使用,这里只会出现

349.两个数组的交集

unordered_set写法:

class Solution {
public:
    vector<int> intersection(vector<int>& nums1, vector<int>& nums2) {
        unordered_set<int> hash;
        for(int n : nums1) hash.insert(n);
        set<int> s;
        sort(nums2.begin(), nums2.end());
        for(int i = 0; i < nums2.size(); i++)
        {
            if(hash.find(nums2[i]) != hash.end()) s.insert(nums2[i]);
        }
        return vector<int>(s.begin(), s.end());
    }
};

数组写法:

class Solution {
public:
    vector<int> intersection(vector<int>& nums1, vector<int>& nums2) {
        vector<int> hash(1001, 0);
        for(int a : nums1) hash[a]++;
        set<int> s;
        for(int i = 0; i < nums2.size(); i++)
        {
            if(hash[nums2[i]]) s.insert(nums2[i]);
        }
        return vector<int>(s.begin(), s.end());
    }
};

两种写法都大差不差,都是先遍历nums1数组用哈希表来存储,第二次遍历nums2数组来记录两个数组的交集元素,存储在set中,由于最后返回的是数组,我们用return vector< int >(s.begin(), s.end());来转换。

454.四数相加II

class Solution {
public:
    int fourSumCount(vector<int>& nums1, vector<int>& nums2, vector<int>& nums3, vector<int>& nums4) {
        unordered_map<int, int> m1, m2;
        for(int a : nums1)
        {
            for(int b : nums2) m1[a+b]++;
        }
        int count = 0;
        for(int a : nums3)
        {
            for(int b : nums4)
            {
                int temp = 0-(a+b);
                if(m1[temp]) count += m1[temp];
            }
        }
        return count;
    }
};

解决过程:将四个数组分成两个对数组,先将前一对数组进行各元素相加,将取得的值存入m1哈希表中,这里采用的哈希表需要记录求和的个数,则需要数值到个数之间的映射,所以这里用到的哈希表为unordered_map,遍历完第一对数组之后,就类似于1.两数相加的题型了,如果在差值在m1中出现过,那么我们就将count累加,注意count = m1[0-(a+b)];而不是count++;

18.四数之和

class Solution {
public:
    vector<vector<int>> fourSum(vector<int>& nums, int target) {
        vector<vector<int>> res;
        sort(nums.begin(), nums.end());
        int n = nums.size();
        for(int i = 0; i < n-3; i++)
        {
            if(i && nums[i] == nums[i-1]) continue;
            for(int j = i+1; j < n-2; j++)
            {
                int left = j+1, right = n-1;
                if(i+1 < j && nums[j] == nums[j-1]) continue;
                if((long long)nums[i]+nums[j]+nums[j+1]+nums[j+2] > target) break;
                if((long long)nums[i]+nums[j]+nums[n-1]+nums[n-2] < target) continue;
                while(left < right)
                {
                    long long temp = (long long)nums[i]+nums[j]+nums[left]+nums[right];
                    if(temp < target) left++;
                    else if(temp > target) right--;
                    else
                    {
                        res.push_back({nums[i], nums[j], nums[left], nums[right]});
                        //cout << i << j << left << right;
                        for(++left; left < right && nums[left] == nums[left-1]; left++);
                        for(--right; left < right && nums[right] == nums[right+1]; right--);
                    }
                }
            }
        }
        return res;
    }
};

这一题的解法与三数之和是同样的,都是用到了双指针的方法,然后外面套两层循环来枚举数组,这里需要注意的点是:在我提交后发现有int类型的溢出错误,我就尝试先将temp的类型改成long long,结果还是报错,查了之后才发现需要在nums数组前面也要强制转化成long long型,这样得出来的数组结果也会向上转型成long long类型。否则在数组累加的过程中会导致先int型溢出了然后无法赋值给temp的情况。

151.反转字符串中的单词

class Solution {
public:
    void func(string& s, int left, int right)
    {
        while(left < right) swap(s[left++], s[right--]);
    }

    void deletespace(string& s)
    {
        //i快指针 j慢指针
        int i, j = 0;
        for(i = 0; i < s.size(); i++)
        {
            //快指针非空 进行替换操作
            if(s[i] != ' ')
            {
                //在每个单词之前手动添加空格
                if(j != 0) s[j++] = ' ';
                //进行替换
                while(i < s.size() && s[i] != ' ') s[j++] = s[i++];
            }
        }
        s.resize(j);
    }

    string reverseWords(string s) {
        //先除去非法空格
        deletespace(s);
        //反转所有字符串
        func(s, 0, s.size()-1);
        //反转每个单词之间的字符
        int start = 0;
        for(int i = 0; i <= s.size(); i++)
        {
            if(i == s.size() || s[i] == ' ')
            {
                func(s, start, i-1);
                start = i+1;
            }
        }
        return s;
    }
};

首先反转单词和反转字符十分像,但是需要发现怎么将反转字符的方法用到反转单词上,这里给出的方法是先将字符串s整体反转,然后再将单词之间内部再次反转,,这样就达到了将字符串中的单词反转的效果了。这里还有一个算法,就是去除不合法的空格:用到的是快慢双指针,与前面的27.移除元素是一个道理,移除元素是将元素的某个val值删除,这里删除空格也就是可以看成将一个数组全部为char型,然后删除字符’ '即可。只不过英语语句有这样一个特点:句子没有前缀空格,单词与单词之间有一个空格,所以如果慢指针j != 0需要在替换之前先添加空格。

459.重复的子字符串

class Solution {
public:

    void getNext(int next[], string s)
    {
        next[0] = 0;
        int j = 0;
        for(int i = 1; i < s.size(); i++)
        {
            while(j > 0 && s[j] != s[i]) j = next[j-1];
            if(s[i] == s[j]) j++;
            next[i] = j;
        }
    }

    bool repeatedSubstringPattern(string s) {
        if(s.empty()) return false;
        int n = s.size();
        int next[n];
        getNext(next, s);
        if(next[n-1] != 0 && n%(n-next[n-1]) == 0) return true;
        return false;
    }
};

这里用到的是kmp算法,getNext函数求相等前后缀表,这个理解起来是在困难,还是先先记下来为妙吧。。。while那行一定不能写成if,也不能少了j>0的条件,这一句是在不相等的情况下。if语句是在两个字符相等的情况下,循环最后将当前next[i]值赋值成j,即当前下标的前后缀相等数。然后就是主函数中判断题目中给出的意思,n-next[n-1]是用来找出最短的符合条件的子串,然后如果能被总长度整除,那么这个字符串可以由某个子串重复构成。这怎么会是简单题???可能简单在于比较好暴力吧,感觉这个kmp还是挺难理解的,加上最后需要取余来判断是否满足条件,这也是很难想到的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值