利用位运算判断字符是否存在

背景

一般情况下,我们判断一个字符串中是否存在某个字符,最常用的方法就是遍历字符串进行逐个匹配的方法。

另外,就是利用Map、Set和二叉搜索树这种结构,降低算法的复杂度。

逐个遍历的方法,很明显算法复杂度是线性增长的。利用Map、Set和二叉搜索树来进行辅助的判断,这种数据结构虽然查找效率相对较高,但是本身会占用较大的存储空间。

在不区分大小写的情况下,我们可以利用位运算来辅助进行判断。

主要思想

利用位运算来辅助判断字符是否存在于某个字符串中,主要是的思路是:

在不区分大小写的情况下进行查找,可以先把整个字符串中的大写字母全部转换成小写字母。

因为,英文字母总共有26个,所以,可以用一个整数来做位映射。

如果某个字符出现,则把整数对应的二进制位上的数字置为1,否则置为0.

然后把要查找的字符也映射成一个整数,然后用两个整数来做按位与运算,如果结果等于0,则字符串中不存在该字符,如果结果不等于0,则字符串中存在该字符。

算法示例

我们使用leetcode 1239,作为示例来展示该方法。

题目介绍

给定一个字符串数组 arr,字符串 s 是将 arr 某一子序列字符串连接所得的字符串,如果 s 中的每一个字符都只出现过一次,那么它就是一个可行解。

请返回所有可行解 s 中最长长度。

题目边界条件

  • 1 <= arr.length <= 16

  • 1 <= arr[i].length <= 26

  • arr[i] 中只含有小写英文字母

思路分析

初看题目,是求最大值一类的问题,以为可以用动态规划来求解这个问题。

我们可以定义一个数组dp,对于数组中的元素dp[i]所代表的含义,在数组arr中范围为[0,i]范围内的元素,形成的可行解字符串的最大长度。

对于,arr中的任意一个元素arr[i],我们有两种选择,作为可行解,不作为可行解。

如果作为可行解,dp[i] = dp[i-1] + arr[i].length();

如果不作为可行解,dp[i] = dp[i-1];

由于我们的目标是求最大值,所以,最终dp[i] = max(dp[i-1] + arr[i].length(), dp[i-1]);

根据,题目的要求,可行解中的字符是不能重复的,所以,在进行选择的时候,我们还需要处理以下两种情况,

  • arr[i]中有重复的字符

  • arr[i]中的字符与dp[i-1]中的选择的字符有重复

如果,arr[i]本身有重复字符,那是肯定不能的选的。

同理,如果arr[i]与dp[i-1]中的字符有重复,那么也不能选。

分析到此处,我们发现,子问题的选择会影响到更大范围的问题的选择,这样就不具有最优子结构了,因此,个人认为这道题目不可以使用动态规划来求解。

因此,本题,很容易想到的另外一种思路就是回溯了。

代码实现

#include<vector>
#include<string>
using namespace std;
​
class Solution{
public:
    int maxLength(vector<string>& arr){
        int n = arr.length();
        vector<int> masks(n, 0);
        
        for(int i = 0; i < n; i++){
            masks[i] = str2int(arr[i]);
        }
        
        return backtrack(masks, arr, 0, 0);
    }
private:
    int str2int(string str){
        int mask = 0;
        for(auto char c: str){
            if((mask & (1 << (c - 'a'))) == 0){
                mask |= (1 << (c - 'a'));
            }else{
                // 字符串本身存在重复字符串
                return 0;
            }
        }
        return mask;
    }
    
    // 回溯算法实现
    int backtrack(vector<int>& masks, vector<string>& arr, int i, int mask){
        if(i == masks.size()) return 0;
        
        if(masks[i] == 0 || (mask & masks[i]) != 0){
            // 存在重复字符串
            return backtrack(masks, arr, i + 1, mask);
        }else{
            int a = backtrack(masks, arr, i + 1, mask | masks[i]) + arr[i].length();
            int b = backtrack(masks, arr, i + 1, mask);
            return max(a, b);
        }
    }
};

总结

在本文的示例中,由于可行解是由不同的字符组成的字符串,因为小写字母总共有26个,故而可行解最长也就是全部的26个英文字母,所以,可以用一个整数来表示字符对应的位映射结果。

还有需要注意的是,示例看起来可以用动态规划来求解,但是实际上并不可行。

位运算是算法中很常见的一种高效的方法。

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值