二分答案的避坑指南

经过研究,本蒟蒻对令人懵逼的二分答案有了一点心得。

 

二分的思想主要分三种:

  1. l和r代表的“成本值”均可行,且有一个ans变量记录当前的最优
  2. l和r代表的“成本值”均可行,最后的答案是l或r
  3. l代表的“成本值”可行,r不可行,最后的答案是l

很多题目都可以转换为第一种思想。定义一个记录答案的变量即可。所以二分答案的难点还是落到了边界的处理和答案的更新上。什么时候用l,什么时候l和r均可?很多问题困扰了不少小白(包括笔者)。由于实力问题(害),这里只是笔者做题的一些避坑方法,帮助你少花时间调试和找到正解。

避坑1.要规避错误,一个要点是清楚什么情况是满足条件但不是最优解,另一种情况自然是不满足条件。在满足条件时要把答案更新,不满足则只更新边界。

避坑2.教练无数次举过一个例子left = 2,right = 3。此时需要一些操作,如令mid = (left + right + 1)/2,或left = mid + 1,或right = mid - 1;具体选择这三中的哪一个,依题而定。

   上题:最长递增子序列

class Solution {
public:

int dp[5000] = {0};

int lengthOfLIS(vector<int>& nums) 
{
    int n = nums.size();

    int len = 1;

    dp[1] = nums[0];

    for(int i=1;i<n;i++)
       {
          if(dp[len] < nums[i]) dp[++len] = nums[i];

          else
            {
                int l = 1,r = len;
                cout<<nums[i]<<" "<<len<<" ";

                while(l <= r)
                   {
                       int mid = (l + r) >> 1;

                       if(dp[mid] < nums[i]) //不能等于o,最后找到一个小于nums[i]的数才是正解! 等于不是严格递增所以不属于正解
                         l = mid + 1;
                       else
                         r = mid - 1;
                   }

                dp[l] = min(dp[l],nums[i]);
                cout<<l<<endl;
            }
       }

    return len;
}
};

当nums[i] 小于等于dp[len]时,我需要找到最大的小于等于nums[i] 的一个数dp[mid],那么必须让它后面的数(如果两数相等就是它本身)更新成nums[i],而L在while结束循环时一定是大于等于right的,因此答案是dp[l],不是dp[r]。

 

posted @ 2021-11-18 17:55  killerboom  阅读(94)  评论(0)    收藏  举报