新的一天,新的开始!!!
面试题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没能顺利通过,还需要看答案,还需要继续提高!!!