题目

给你一个二进制字符串 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 <= 10^9

来源:力扣(LeetCode)
链接:https://leetcode.cn/problems/longest-binary-subsequence-less-than-or-equal-to-k
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

思路

返回的s子序列(记为t)的二进制表示需要满足不大于k,因此除去t的前导0后,要么t的长度小于k的二进制长度(记为m),要么长度相等且十进制表示不大于k。

对于s长度和m之间的关系,可以分为两种情况:

1、当s长度不小于m,我们从低位使用一个长度为m的滑窗,向左滑动,依次比较滑窗内二进制串和k的二进制串的大小关系,如果前者不大于后者,那么找到了一种解,再加上前面的0充当前导0即可。

有可能遍历完s之后,都找不到一个不小于k的子序列,这种情况下,一定可以取长度为m-1的序列,满足该序列的十进制不大于k。

为什么找到的第一个解一定是最长的呢,因为我们是倒着遍历,前面的前导0一定是最多的。

2、当s长度小于m,s串本身的十进制一定小于k,返回s长度即可。

代码

  1. class Solution {
  2. public int longestSubsequence(String s, int k) {
  3. int n = s.length();
  4. String kstr = Integer.toBinaryString(k);
  5. int m = kstr.length();
  6. // s长度小于k二进制长度,s十进制一定小于k,返回s.length()
  7. if (n < m) {
  8. return n;
  9. }
  10. int ans = 0;
  11. int i = 0;
  12. // 从s的低位开始取长度为m的滑窗,和k进行比较
  13. for (i = n - m; i >= 0; i--) {
  14. // 如果s中长度为m的子序列不大于k,就表示找到了一个解,长度至少为m
  15. if (compare(s.substring(i, i + m), kstr)) {
  16. ans = m;
  17. break;
  18. }
  19. }
  20. // 没有找到合法的解,但是长度m-1的序列一定小于k
  21. if (ans == 0) {
  22. return m - 1;
  23. }
  24. // 加上前面的0充当前导0
  25. for (int j = i - 1; j >= 0; j--) {
  26. if (s.charAt(j) == '0') {
  27. ans++;
  28. }
  29. }
  30. return ans;
  31. }
  32. // 从高位依次比较s和t,如果s不大于t,那么返回true,反之返回false
  33. private boolean compare(String s, String t) {
  34. int m = s.length();
  35. for (int i = 0; i < m; i++) {
  36. if (s.charAt(i) > t.charAt(i)) {
  37. return false;
  38. } else if (s.charAt(i) < t.charAt(i)){
  39. return true;
  40. }
  41. }
  42. return true;
  43. }
  44. }