算法&数据结构学习(2) 剑指offer刷题笔记(3)

新的一天,新的开始!!!

面试题15. 二进制中1的个数

在这里插入图片描述
思路:本题主要考察的是一个位运算。主要解题思路有两种:1、位与&运算,对于n&1如果为1,即末位是1,否则为0,借助这个思路,可以对n的没一位,按位与1进行判断,为1则cnt++,然后将n右移一位,直至n为零;2、方法二需要一些技巧:即n&(n-1)这个操作,这个操作会将n的最右侧的1置零,这样逐位将n的最右侧的1置零并统计个数,直至n为0即可。
代码如下:

class Solution {
public:
    int hammingWeight(uint32_t n) {
        //法2:巧用n&(n-1)
        int cnt = 0;
        while(n)
        {
            ++cnt;
            n &=n-1;
        }
        return cnt;
   
        /*/法1:逐位统计
        int cnt = 0;
        while(n)
        {
            if(n&1) cnt++;
            n >>=1;
        }
        return cnt;*/
    }
};

面试题16. 数值的整数次方

在这里插入图片描述
思路:本题比较好的方法为快速幂。详见百科:快速幂
代码如下:

class Solution {
public:
    double myPow(double x, int n) {
        //思路:快速幂
        if(n==0) return 1;
        long long k = n;//极端情况INT_MIN求相反数时会溢出,所以需要用long long
        double res = 1.0;
        if(k<0)//处理指数为负的情况
        {
            k = -k;
            x = 1/x;
        }
        while(k)
        {
            if(k&1) res *= x;//如果指数为奇数,即需要额外乘以一个x
            x *= x;
            k >>= 1;
        }
        return res;
    }
};

面试题17. 打印从1到最大的n位数

在这里插入图片描述
此处是leetcode上的题目解法,与《剑指offer》书中愿意差别较大,原书中考查的是大数打印的问题,而leetcode上的题目却没体现,因此后序会贴出大数打印的思路及代码。

class Solution {
public:
    vector<int> printNumbers(int n) {
        //leetcode题意
        vector<int> res;
        if(!n) return res;
        for(int i=1, max=pow(10,n); i<max; ++i)
            res.push_back(i);
        return res;
    }
};

大数打印代码如下:

class Solution{

public:
	vector<int> printNumbers(int n){
		if(n<=0) return res;
		string number(n,'0');
		while(!Increment(number))
			saveNumber(number);
		return res;
	}
private:
	vector<int> res;
	bool Increment(string& number)
	{
		bool isoverflow = false;//越界标记
		int carrybit = 0;//存储进位
		int len = number.size();
		for(int i=len-1; i>=0; --i)
		{
			int tmp = number[i] - '0' + carrybit;
			if(i==len-1)//如果是末位数,则+1
				++tmp;
			if(tmp>=10)//存在进位情况
			{
				if(i==0)//最大位存在进位,即超过最大值了
					isoverflow = true;
				else//处理进位,同时更新该位置的值
				{
					carrybit = 1;
					number[i] = tmp - 10 + '0';
				}
			}else//没有进位发生
			{
				number[i] = tmp+'0';
				break;//没有进位可以直接跳出循环
			}
		}
		return isoverflow;
	}
	void saveNumber(const string& number)
	{
		auto index = number.find_first_not_of('0');
		if(index!=string::npos)
			res.push_back(stoi(number.substr(index)));
	}
};

面试题18. 删除链表的节点

在这里插入图片描述
本题也可只用单指针即可。此外,此题在《剑指offer》书中的原题是给定一个已知的单链表节点,要求在O(1)时间内删除该节点。大致思想是:由于删除链表的节点最先想到一定是找到删除节点的前驱节点,然后将待删除节点的后继成为前驱节点的后继即可。但是单向链表往往需要顺序遍历才能找到前驱节点,因此需要消耗O(n)时间,那如何实现O(1)找到前驱节点呢?换个思路,如果我们把待删除节点的后继的值复制给待删除节点,则相当于把后继节点成为了前驱节点的后继,随后只需要删除后继节点值即可。

class Solution {
public:
    ListNode* deleteNode(ListNode* head, int val) {
        //双指针法
        if(!head) return nullptr;
        if(head->val==val) return head->next;
        auto pre = head;
        auto cur = head->next;
        while(cur)
        {
            if(cur->val==val)
            {
                pre->next = cur->next;
                break;
            }else
            {
                pre = cur;
                cur = cur->next;
            }
        }
        return head;
    }
};

面试题19. 正则表达式匹配

在这里插入图片描述
递归解法(思路都在注释):

class Solution {
public:
    bool isMatch(string s, string p) {
        return isMatch(s.c_str(),p.c_str());
    }
    //为了方便判断串尾,将string转换成C风格字符串更好处理
    bool isMatch(const char*s, const char* p)
    {
        //递归出口,如果串p到达了尾部,则判断串s是否也到达尾部,不是则不能匹配,是说明可以完全匹配
        if(*p==0) return *s==0;
        //判断文本串s的是否到串尾或能否和模式串匹配
        auto fisrt_match = *s && (*s==*p || *p=='.');
        //如果*(p+1)=='*',则分两种情况
        if(*(p+1) == '*')
            return isMatch(s,p+2)//情况1:当前s和p不匹配,递归尝试s和p+2是否匹配,即*代表零个*p 
            || (fisrt_match && isMatch(++s,p));//情况2:如果当前已经匹配过了,即fisrs_match为真,则尝试++s和p是否匹配,即*号尝试更多的匹配
        else
            return fisrt_match && isMatch(++s,++p);//如果当前已经匹配了,且后续不是*号,则两个串都前移再继续尝试匹配
    }
};

动态规划解法:

class Solution {
public:
    bool isMatch(string s, string p) {
       //动态规划
       int slen = s.size(),plen = p.size();
       if(slen==0 && plen==0) return true;
       //dp[i][j]表示文本串s的前i个字符和模式串p的前j个字符能否匹配
       vector<vector<bool>> dp(slen+1,vector<bool>(plen+1,false));
       dp[0][0] = true;//空串一定匹配
       //处理文本串为空的情形
       for(int j=1; j<=plen; ++j)
       {
           //j==1,首字符一定是字母,所以肯定不能与空串匹配
           if(j==1) dp[0][j] = false;
           //如果p[j-1]=='*',则可以判断,j-1和j-2处两个字符是不影响匹配的
           //因此,dp[0][j]能否匹配取决于[0,j-2]能否匹配,即dp[0][j] = dp[0][j-2]
           if(p[j-1]=='*' && dp[0][j-2]) dp[0][j] = true;
       }
        //当文本串s不为空
        for(int i=1; i<=slen; ++i)
            for(int j=1; j<=plen; ++j)
            {
                //第一种匹配情况:两个字符相等或者p[j-1]=='.'
                if(s[i-1]==p[j-1] || p[j-1]=='.')
                    //当前两个可以匹配,则其匹配结果与dp[i-1][j-1]一样
                    dp[i][j]=dp[i-1][j-1];
                //如果模式串p的字符是*,则需要回到*的前一个字符看能否匹配
                else if(p[j-1]=='*')
                {
                    if(j<2) dp[i][j]=false;
                    //如果j-2的字符能和i-1的字符匹配
                    if(p[j-2]==s[i-1] || p[j-2]=='.')
                        // 此时存在两种情况,即所谓的*号能匹配的第j-2个字符的个数:可能匹配0个,也可能匹配多个
                        //情况1:如aab aabb*此时i=3,j=5,此时j-1是*,j-2是b,J-1和i-1都是b可以匹配,但是在此之前
                        //      即i=3,j=3时,也就是aab aab时他们已经完成了匹配即dp[3][3]为true,因此这个*号不需要任何匹配
                        //      即dp[i][j] = dp[i][j-2]即可。
                        //情况2:如果需要匹配多个,则存在这种情形aabb aab*
                        //      此时i=4,j=4,我们需要匹配dp[i][j],由于p[j-2]==s[i-1]
                        //      那由于i,j是内外循环,因此到i=4,j=4的时候一定进行过i=3,j=4的匹配判断,所以我们可以忽略第4个b
                        //      转而继续往前寻求更多的b和当前的串匹配,即dp[4][4] = dp[3][4]即aab与aab*匹配情况
                        //      又因为p[j-2]==s[i-2],即第3个b仍然匹配,因此我们继续相同的操作,忽略第3个b,继续往前
                        //      即dp[4][4] = dp[3][4] = dp[2][4],即aa与aab*的匹配而这种情形当然依然遍历过了
                        dp[i][j] = dp[i-1][j] || dp[i][j-2];
                    //如果j-2与i-1字符不能匹配,则丢弃j-1,j-2两个字符
                    //此时能否匹配则取决于前j-2个字符,即dp[i][j]=dp[i][j-2]
                    else
                        dp[i][j] = dp[i][j-2];
                }
                //其他情况下都是不可能匹配
                else
                    dp[i][j]=false;               
            }
        return dp[slen][plen];
    }
};

今日的刷题笔记顺利完成,正则表达式匹配确实值得多看几遍,这算是遇到的第三遍,写起来还是有一些小的bug没能顺利通过,还需要看答案,还需要继续提高!!!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值