题目描述

leetcode2311. 小于等于 K 的最长二进制子序列

给你一个二进制字符串 s 和一个正整数 k 。

请你返回 s 的 最长 子序列,且该子序列对应的 二进制 数字小于等于 k 。

注意:

  • 子序列可以有 前导 0 。
  • 空字符串视为 0 。
  • 子序列 是指从一个字符串中删除零个或者多个字符后,不改变顺序得到的剩余字符序列。

 

示例 1:

输入:s = "1001010", k = 5
输出:5
解释:s 中小于等于 5 的最长子序列是 "00010" ,对应的十进制数字是 2 。
注意 "00100" 和 "00101" 也是可行的最长子序列,十进制分别对应 4 和 5 。
最长子序列的长度为 5 ,所以返回 5 。

示例 2:

输入:s = "00101001", k = 1
输出:6
解释:"000001" 是 s 中小于等于 1 的最长子序列,对应的十进制数字是 1 。
最长子序列的长度为 6 ,所以返回 6 。

 

提示:

  • 1 <= s.length <= 1000
  • s[i] 要么是 '0' ,要么是 '1'
  • 1 <= k <= 109

题解——记忆化搜索

思路

可以看做背包问题,每个字符都有选与不选两种状态
now记录当前生成的子串的十进制数字,length记录子串的长度,用一个pos记录选到哪个位置了
从后往前遍历整个字符串,对于0,可以选也可以不选,不改变当前值,只对长度有影响;对于1,只有当加上它后仍然能小于k,才选它,否则不选
当得到更大的length后,记录下来,如果一直遍历到了开头,返回
同时,采用记忆化搜索,记录选到的pos所达到过的最小值,如果当前值比记录值大,说明不会找到更好的解,直接返回

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
//从后往前尝试,每个字符都有两种状态
class Solution {
public:
int ans=0;
unordered_map<int,int> memo;//备忘录,记录当前位置已经达到的最小值
void solve(string &s,long now,int pos,int k,int length)
{
if(memo.find(pos)!=memo.end()&&memo[pos]<=now)
return ;
memo[pos]=now;
if(length>ans)
ans=length;
if(pos==-1)
return ;
if(s[pos]=='0')
solve(s,now,pos-1,k,length+1);//如果是0,直接选
else if(now+pow(2,length)<=k)//如果是1,选它,会使值增加2^length,只有仍然满足要求时,才能选
solve(s,now+pow(2,length),pos-1,k,length+1);//选
solve(s,now,pos-1,k,length);//都可以不选
}
int longestSubsequence(string s, int k) {
solve(s,0,s.size()-1,k,0);
return ans;
}
};