剑指offer 打卡计划 | 每日进步一点点 | 第十九天

91 阅读1分钟

图片.png

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第21天,点击查看活动详情

剑指 Offer 51. 数组中的逆序对

思路

(归并排序) O(nlogn)

归并排序模板:

 const int maxn = 1e5 + 10;
 int q[maxn], tmp[maxn];
 void merge_sort(int q[], int l, int r)
 {
     if (l >= r)  return;      //如果只有一个数字或没有数字,则无需排序
     int mid = (l + r ) / 2;
     merge_sort(q, l, mid);       //分解左序列
     merge_sort(q, mid + 1, r);   //分解右序列
     int k = l, i = l, j = mid + 1;
     while (i <= mid && j <= r)   //合并
     {
         if (q[i] <= q[j]) tmp[k++] = q[i++];
         else tmp[k++] = q[j++];
     }
     while (i <= mid) tmp[k++] = q[i++];    //复制左边子序列剩余
     while (j <= r)   tmp[k++] = q[j++];    //复制右边子序列剩余
     for (int i = l; i <= r; i++) q[i] = tmp[i];
 }

在归并排序的合并操作中,我们假设左右两个区间元素为:

左边:{3 4 7 9} 右边:{1 5 8 10}

那么合并操作的第一步就是比较31,然后将1取出来放到辅助数组中,这个时候我们发现,右边的区间如果是当前比较的较小值,那么其会与左边剩余的数字产生逆序关系,也就是说13479都产生了逆序关系,因此我们可以一下子统计出有4对逆序对。接下来34取下来放到辅助数组后,5与左边剩下的79产生了逆序关系,我们可以统计出2对。依此类推,89产生1对,那么总共有4 + 2 + 1对。这样统计的效率就会大大提高,便可较好地解决逆序对问题。

而在算法的实现中,我们只需略微修改原有归并排序,当右边序列的元素为较小值时,就统计其产生的逆序对数量,即可完成逆序对的统计。

图片.png

时间复杂度分析: 归并排序的时间复杂度为O(nlogn)。

c++代码

 class Solution {
 public:   
     int reversePairs(vector<int>& nums) {
         return merge(nums, 0, nums.size() - 1);
     }
 ​
     int merge(vector<int>&nums, int l, int r){
         if(l >= r) return 0; //序列中只有一个数
         int mid = l + r>> 1;
         int res = merge(nums, l, mid) + merge(nums, mid + 1, r);
         vector<int> tmp;
         int i = l, j = mid + 1;
         while(i <= mid && j <= r){
             if(nums[i] <= nums[j]) tmp.push_back(nums[i++]);
             else{
                 res += mid - i + 1;
                 tmp.push_back(nums[j++]);
             }
         }
         while(i <= mid) tmp.push_back(nums[i++]);
         while(j <= r)   tmp.push_back(nums[j++]);   
         int k = l;
         for(int x : tmp) nums[k++] = x;
         return res;
     }
 };

剑指 Offer 52. 两个链表的第一个公共节点

(链表,指针扫描) O(n)

这题的思路很巧妙,我们先给出做法,再介绍原理。

算法步骤:

  1. 用两个指针分别从两个链表头部开始扫描,每次分别走一步;

  2. 如果指针走到null,则从另一个链表头部开始走;

  3. 当两个指针相同时:

    • 如果指针不是null,则指针位置就是相遇点;
    • 如果指针是null,则两个链表不相交;

此题我们画图讲解,一目了然:

1、两个链表不相交:

图片.png

ab 分别代表两个链表的长度,则两个指针分别走 a + b 步后都变成 null

2 、两个链表相交:

图片.png

则两个指针分别走 a + b + c 步后在两链表交汇处相遇。

时间复杂度分析: 每个指针走的长度不大于两个链表的总长度,所以时间复杂度是O(n)。

c++代码

 /**
  * Definition for singly-linked list.
  * struct ListNode {
  *     int val;
  *     ListNode *next;
  *     ListNode(int x) : val(x), next(NULL) {}
  * };
  */
 class Solution {
 public:
     ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
         auto pA = headA, pB = headB;
         while(pA != pB) {
             if(pA) pA = pA->next;
             else pA = headB;
             if(pB) pB = pB->next;
             else pB = headA;
         }
         return pA;
     }
 };

剑指 Offer 53 - I. 在排序数组中查找数字 I

思路

c++代码

 class Solution {
 public:
     int search(vector<int>& nums, int target) {
         if(!nums.size()) return  0;
         int l = 0, r = nums.size() - 1;
         while(l < r)       //查找target的开始位置
         {
             int mid = (l + r) / 2;
             if(nums[mid] >= target) r = mid;
             else l = mid + 1;
         }
         if(nums[r] != target) return 0 ;  //查找失败
         int begin = r;     //记录开始位置
         l = 0, r = nums.size() - 1;
         while(l < r)       //查找tatget的结束位置
         {
             int mid = (l + r + 1) / 2;
             if(nums[mid] <= target) l = mid;
             else r = mid - 1;
         }
         int end = r;       //记录结束位置      
         return end - begin + 1;
     } 
 };