力扣基础笔记

4.寻找两个正序数组的中位数
二分查找
  • 使用while(true)循环代替递归函数传参,使用两个idx避免修改数组
int getKthElement(const vector<int>& nums1, const vector<int>& nums2, int k)
{
    int m = nums1.size();
    int n = nums2.size();

    int idx1 = 0, idx2 = 0;
    while (true)
    {
//        cout<<"idx1,idx2="<<idx1<<' '<<idx2<<endl;
//        cout<<"k="<<k<<endl;
        // 边界情况
        if (idx1 == m) // nums1空,返回nums2中第(idx1+k-1)小的值
        {
            return nums2[idx2 + k - 1];
        }
        else if (idx2 == n)
        {
            return nums1[idx1 + k - 1];
        }
        if (k == 1)
        {
            return min(nums1[idx1], nums2[idx2]);
        }
        // 一般情况
        int newIdx1 = min(idx1 + k / 2 - 1, m - 1);
        int newIdx2 = min(idx2 + k / 2 - 1, n - 1);
        int tmp1 = nums1[newIdx1], tmp2 = nums2[newIdx2];
        if (tmp1 <= tmp2)
        {
            // 注意这两行的顺序
            k -= (newIdx1 - idx1 + 1);
            idx1 = newIdx1 + 1;
        }
        else
        {
            k -= (newIdx2 - idx2 + 1);
            idx2 = newIdx2 + 1;
        }
    }
}

double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2)
{
    int total = nums1.size() + nums2.size();
    if (total % 2 == 1)
    {
        return (double)getKthElement(nums1, nums2, (total) / 2 + 1);
    }
    else
    {
        return ((double)getKthElement(nums1, nums2, (total / 2)) + (double)getKthElement(nums1, nums2, (total / 2) + 1)) / 2;
    }
}

15.三数之和
排序+三重循环暴力方法

要求三元组不重复,于是

  • 保证元组内部的单调性(非严格)

  • if(j==i+1||nums[j]!=nums[j-1])=true时进行下一组循环,否则可能出现

    -4,-1,-1,0,1,2
    

    [-1,0,1]出现两次

vector<vector<int>> threeSum(vector<int>& nums) {
    sort(nums.begin(),nums.end());
    int len=nums.size();
    vector<vector<int>> res;
    int idx=0;
    for(int i=0;i<len;i++){
        if(i==0||nums[i]!=nums[i-1]){
            for(int j=i+1;j<len;j++){
                if(j==i+1||nums[j]!=nums[j-1]){
                    for(int k=j+1;k<len;k++){
                        if(k==j+1||nums[k]!=nums[k-1]){
                            if(nums[i]+nums[j]+nums[k]==0){
                                vector<int> v;res.push_back(v);
                                res[idx].push_back(nums[i]);
                                res[idx].push_back(nums[j]);
                                res[idx].push_back(nums[k]);
                                idx++;
                            }
                        }
                    }
                }
            }
        }
    }
    return res;
}

排序+双指针

对于给定的 a , b a,b a,b c c c是确定的。当右移二重循环的指针,即 b b b变大时, c c c将变小,也就是三重循环的指针左移。在这种一个指针递增、一个指针递减的情况下,可以使用双指针,通常为leftright,将枚举的复杂度从 O ( N 2 ) O(N^2) O(N2)降到 O ( N ) O(N) O(N).

算法分析
  1. 特判,当数组长度<3时返回空,当nums[0]>0时返回空
  2. 对数组进行排序
  3. 遍历,
    • nums[i]>0跳出循环
    • 在结束每个i的比较后,跳过重复元素
    • 构造双指针,left=i+1,right=n-1,当left<right时执行循环
      • x+y+z<0,右移left
      • x+y+z>0,座椅right
      • x+y+z=0,保存结果,分别将左右指针移动到下一个不等值位置
vector<vector<int>> threeSum(vector<int>& nums)
{
    vector<vector<int>> res;
    if (nums.size() < 3) // 数组长度小于3
        return res;
    sort(nums.begin(), nums.end());
    if (nums[0] > 0) // 最小的元素大于0
        return res;
    int i = 0;
    while (i < nums.size())
    {
        if (nums[i] > 0) // 最小的元素大于0,提前终止循环
            break;

        int left = i + 1, right = nums.size() - 1;
        while (left < right)
        {
            long long x = nums[i];
            long long y = nums[left];
            long long z = nums[right];
            if (x + y + z < 0)
            {
                left++;
            }
            else if (x + y + z > 0)
            {
                right--;
            }
            else if (x + y + z == 0)
            {
                // res.push_back({x,y,z});  // 这样push进的是longlong
                res.push_back({nums[i], nums[left], nums[right]});
                // 相同的y,z不应再出现
                while (left < right && nums[left + 1] == nums[left])
                {
                    left++;
                }
                while (left < right && nums[right - 1] == nums[right])
                {
                    right--;
                }
                // 此时到了最后一个与原y,z相等的地方,最后移动一位
                left++;
                right--;
            }
        }
        while (i + 1 < nums.size() && nums[i + 1] == nums[i])
        {
            i++;  // 跳过重复的x
        }
        // 此时到了最后一个与原x相等的地方,最后移动一位
        i++;
    }
    return res;
}
19.删除链表的倒数第N个结点

需要自定义一个表头,防止head被删去后返回错误

得到目标删除结点的前一个结点

ListNode* removeNthFromEnd(ListNode* head, int n)
{
    ListNode* top = new ListNode(0, head);
    stack<ListNode*> st;
    ListNode* cur = top;
    while (cur)
    {
        st.push(cur);
        cur = cur->next;
    }
    for (int i = 0; i < n; i++)
    {
        st.pop();
    }
    ListNode* pre = st.top();
    ListNode* tmp = pre->next;
    pre->next = tmp->next;
    // delete tmp;
    return top->next;
}
双指针

first指针从第 n n n个结点出发,second从第 0 0 0个结点出发,则当first到达表尾NULL时,second到达倒数第 n n n个结点。

为了方便起见,同样需要得到目标删除结点的前一个结点,即second从表头哑结点dummy出发。

ListNode* removeNthFromEnd(ListNode* head, int n)
{
    // 表头
    ListNode* dummy = new ListNode(-1, head);
    ListNode* first = head;
    ListNode* second = dummy;
    for (int i = 0; i < n; i++)
    {
        first = first->next;
    }
    while (first)
    {
        first = first->next;
        second = second->next;
    }
    second->next = second->next->next;
    return dummy->next;
}
75.颜色分类
单指针两次循环
void sortColors(vector<int>& nums)
{
    int n = nums.size();
    int ptr = 0; // 0~(ptr-1)都是0
    for (int i = 0; i < n; i++)
    {
        if (nums[i] == 0)
        {
            swap(nums[i], nums[ptr]);
            ptr++;
        }
    }
    // 0~(ptr-1)都是0和1
    for (int i = ptr; i < n; i++)
    {
        if (nums[i] == 1)
        {
            swap(nums[i], nums[ptr]);
            ptr++;
        }
    }
}
双指针(0,1)一次循环
  • 扫到1,交换ptr1iptr1右移
  • 扫到0,如果头部0的右侧有连续的1,则ptr0指向1,交换ptr0i将使1被挪到后面,所以要将i位交换回ptr1
    • 第二个判断处可以用else if,也可以用if
void sortColors(vector<int>& nums)
{
    int n = nums.size();
    int ptr0 = 0, ptr1 = 0;
    for (int i = 0; i < n; i++)
    {
        if (nums[i] == 1)
        {
            swap(nums[i], nums[ptr1]);
            ptr1++;
        }
        if (nums[i] == 0)
        {
            swap(nums[i], nums[ptr0]);
            if (ptr0 < ptr1) // 如果0后面是连续的1,会让1被交换出去
                swap(nums[i], nums[ptr1]);
            ptr0++;
            ptr1++;
        }
    }
}
双指针(0,2)一次循环
  • i扫到ptr2为止

  • 扫到 0 0 0,交换ptr0iptr0右移

  • 扫到 2 2 2,交换ptr2iptr2左移。由于交换ptr2i时可能使得nums[i]还是 2 2 2(例如[2,1,2]),所以需要不断地交换直到nums[i]不为 2 2 2

void sortColors(vector<int>& nums)
{
    int n = nums.size();
    int ptr0 = 0, ptr2 = n - 1;
    for (int i = 0; i < n; i++)
    {
        while (i <= ptr2 && nums[i] == 2)
        {
            swap(nums[i], nums[ptr2]);
            ptr2--;
        }
        if (nums[i] == 0)
        {
            swap(nums[i], nums[ptr0]);
            ptr0++;
        }
    }
}
76.最小覆盖子串
基本滑动窗口

设置左右指针,滑动右指针至包含所有 t t t中字符,再滑动左指针夹出最小窗口,记录窗口大小。

int book[127] = {0};
int bookt[127] = {0};

string minWindow(string s, string t)
{
    // 特判
    if (s.length() < t.length())
        return "";
    if (s.length() == t.length())
    {
        if (!s.compare(t))
            return s;
    }
    // 扫入bookt,注意t中各元素可能出现不止一次
    for (int i = 0; i < t.length(); i++)
    {
        bookt[t[i]]++;
    }
    int left = 0, right = 0;
    int finalLeft = 0;
    int res = s.length() + 1;
    while (right < s.length())
    {
        book[s[right]]++;
        int flag = 1; // 是否包含t中所有元素
        for (int i = 0; i < t.length(); i++)
        {
            if (book[t[i]] < bookt[t[i]])
            {
                flag = 0;
                break;
            }
        }
        if (!flag)
        {
            right++;
        }
        else  // 开始右滑left
        {
            while (left < right)
            {
                if (t.find(s[left]) == t.npos ||
                        (t.find(s[left]) != t.npos && book[s[left]] > bookt[s[left]]))
                {
                    book[s[left]]--;
                    left++;
                }
                // 左指针已经到达最右端
                else break;
            }
            int tmp = right - left + 1;
            if (tmp < res)
            {
                res = tmp;
                finalLeft = left;
            }
            // 右移窗口
            book[s[left]]--;
            left++;
            right++;
        }
    }
    // 没有出现过任何满足的子串
    if (res == s.length() + 1)
        res = 0;
    return s.substr(finalLeft, res);
}
142.环形链表Ⅱ
哈希
ListNode *detectCycle(ListNode *head)
{
    set<ListNode*> visited;
    while (head)
    {
        if (visited.count(head))
        {
            return head;
        }
        visited.insert(head);
        head = head->next;
    }
    return NULL;
}
双指针

slow每次走一位,fast每次走两位,如果链表中存在环,它们必将在环中相遇

详解

ListNode *detectCycle(ListNode *head)
{
    ListNode* slow = head;
    ListNode* fast = head;
    while (fast)
    {
        slow = slow->next;
        if (!fast->next)
        {
            return NULL;
        }
        fast = fast->next->next;
        if (fast == slow) // 相遇
        {
            ListNode* ptr = head;
            while (ptr != slow)
            {
                ptr = ptr->next;
                slow = slow->next
            }
            return ptr;
        }
    }
    return NULL;
}
148.排序链表
插入排序

维护 lastSorted 为链表的已排序部分的最后一个节点,更新cur时使用cur=lastSorted->next,这一点与数组不同。如果使用cur=cur->next,则由于cur->next可能已经改变,无法指向想要的结点。

ListNode* sortList(ListNode* head)
{
    if (head)
    {
        ListNode* dummy = new ListNode(0, head);
        ListNode* lastSorted = head; // 已排序部分的最后一个结点
        ListNode* cur = head->next;
        while (cur)
        {
            if (cur->val >= lastSorted->val)
            {
                lastSorted = cur;
            }
            else
            {
                ListNode* pre = dummy;
                while (pre->next->val <= cur->val)
                {
                    pre = pre->next;
                }
                lastSorted->next = cur->next;
                cur->next = pre->next;
                pre->next = cur;

            }
            cur = lastSorted->next;
        }
        return dummy->next;
    }
    return head;
}
归并排序
自顶向下

时间复杂度为 n O ( log ⁡ n ) nO(\log n) nO(logn),空间复杂度为 O ( log ⁡ n ) O(\log n) O(logn)

用快慢指针查找中点:快指针一次走两个,慢指针一次走一个,当快指针到达表尾时慢指针到达中点

Merge中的while(tmp1)while(tmp2)块,由于待合并链表长度差至多为 1 1 1,可以用if代替while

ListNode* sortList(ListNode* head)
{
    return sortList(head, NULL);
}

ListNode* sortList(ListNode* head, ListNode* tail)
// 排序[head,tail)
{
    if (!head)
        return head;
    if (head->next == tail)
        // 仅有head一个元素
    {
        head->next = NULL;
        return head;
    }
    // 用快慢指针找到链表中点
    ListNode* slow = head, *fast = head;
    while (fast != tail)
    {
        slow = slow->next;
        fast = fast->next;
        if (fast != tail)
            fast = fast->next;
    }
    ListNode* mid = slow;
    ListNode* head1 = sortList(head, mid);
    ListNode* head2 = sortList(mid, tail);
    ListNode* res = Merge(head1, head2);
    return res;
}

ListNode* Merge(ListNode* head1, ListNode* head2)
{
    ListNode* dummy = new ListNode(0);
    ListNode* tmp = dummy, *tmp1 = head1, *tmp2 = head2;
    while (tmp1 && tmp2)
    {
        if (tmp1->val <= tmp2->val)
        {
            tmp->next = tmp1;
            tmp1 = tmp1->next;
        }
        else
        {
            tmp->next = tmp2;
            tmp2 = tmp2->next;
        }
        tmp = tmp->next;
    }
    while (tmp1)
    {
        tmp->next = tmp1;
        tmp1 = tmp1->next;
        tmp = tmp->next;
    }
    while (tmp2)
    {
        tmp->next = tmp2;
        tmp2 = tmp2->next;
        tmp = tmp->next;
    }
    return dummy->next;
}
自底向上

时间复杂度为 n O ( log ⁡ n ) nO(\log n) nO(logn),空间复杂度为 O ( 1 ) O(1) O(1)

ListNode* sortList(ListNode* head)
{
    if (!head)
        return head;
    int len = 0;
    ListNode* tmp = head;
    while (tmp)
    {
        len++;
        tmp = tmp->next;
    }
    ListNode* dummy = new ListNode(0, head);
    for (int subLen = 1; subLen < len; subLen *= 2)
    {
        ListNode* pre = dummy, *cur = dummy->next;
        while (cur)
        {
            // 获得一个长为subLen的子串
            ListNode* head1 = cur;
            for (int i = 1; i < subLen && cur->next; i++)
                cur = cur->next;
            ListNode* head2 = cur->next;
            cur->next = NULL;
            // 获得第二个长为subLen的子串
            cur = head2;
            for (int i = 1; i < subLen && cur && cur->next; i++)
                cur = cur->next;
            ListNode* next = NULL;
            if (cur)
            {
                next = cur->next;
                cur->next = NULL;
            }
            // 更新pre(结果链表)
            // 更新cur(过程链表)
            ListNode* merged = Merge(head1, head2);
            pre->next = merged;
            while (pre->next)
                pre = pre->next;
            cur = next;
        }
    }
    return dummy->next;
}
234.回文链表

注意比较时比较node->val而不是node

数组

将结点值压进数组,左右指针向内逐个收缩

bool isPalindrome(ListNode* head)
{
    vector<int> v;
    ListNode* p = head;
    while (p)
    {
        v.push_back(p->val);
        p = p->next;
    }
    // 注意int强制转换
    for (int i = 0, j = (int)v.size() - 1; i < j; i++, j--)
    {
        if (v[i] != v[j])
        {
            return false;
        }
    }
    return true;
}

递归

利用堆栈特性,将栈顶的cur指针指向表尾,将全局指针font初始化为表头

ListNode* font;

bool isPalindromeRecursive(ListNode* cur)
{
    if (cur)
    {
        if (!isPalindromeRecursive(cur->next))
            return false;
        if (font->val != cur->val)
            return false;
        font = font->next;
    }
    return true;
}

bool isPalindrome(ListNode* head)
{
    font = head;
    return isPalindromeRecursive(head);
}

283.移动零
双指针
void moveZeroes(vector<int>& nums)
{
    int n = nums.size();
    int left = 0, right = 0;
    while (right < n)
    {
        if (nums[right])
        {
            swap(nums[left], nums[right]);
            left++;
        }
        right++;
    }
}
直接修改做法
void moveZeroes(vector<int>& nums)
{
    int n = nums.size();
    int idx = 0;
    for (int i = 0; i < n; i++)
    {
        if (nums[i])
        {
            nums[idx++] = nums[i];
        }
    }
    for (int i = idx; i < n; i++)
        nums[i] = 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值