每日算法(1)
每日算法(1)
数字统计

代码:(C++)
#include <iostream>using namespace std;int main()
{int l,r;cin>>l>>r;int ret=0;for(int i=l;i<=r;i++){int tmp=i;while(tmp){if(tmp%10==2) ret++;tmp/=10; }}cout<<ret<<endl;return 0;
}
简单直接的回答是:设置 tmp作为临时变量是为了在分解数字的每一位时,不破坏原始的循环变量 i。
让我们用一个具体的例子来解释为什么这很重要:
举例说明
假设现在循环到 i = 123,我们要统计它里面有几个2。
-
如果没有
tmp,直接操作i:while (i != 0) { // i 初始为 123if (i % 10 == 2) { // 检查个位数 3count++;}i = i / 10; // 问题就出在这里!i 被改成了 12 }- •第一次循环后,
i从 123 被除以10,变成了 12。 - •然后
for循环的i++会让i变成 13。 - •结果:数字 123 没有被完整检查(只检查了个位),并且整个循环序列被完全打乱了,直接跳过了 124, 125 等很多数字。最终结果肯定是错误的。
- •第一次循环后,
-
使用
tmp作为临时变量(正确的做法):int tmp = i; // 创建 i 的副本 while (tmp != 0) { // 对 tmp 进行操作,不影响 iif (tmp % 10 == 2) {count++;}tmp = tmp / 10; // 即使把 tmp 除以10变成0,原始的 i 也毫发无伤 }- •我们只是在
tmp这个“副本”或“替身”上进行分解操作。 - •无论
tmp如何变化,原始的循环变量i始终保持不变,for循环可以正常地i++,检查下一个数字。
- •我们只是在
总结
使用临时变量 tmp | 直接使用 i |
|---|---|
保护数据:不破坏原始循环变量 i的值。 | 破坏数据:修改了 i,导致循环失控。 |
逻辑清晰:i负责遍历每个数,tmp负责分解每个数的各位,职责分明。 | 逻辑混乱:一个变量 i承担了两个职责,互相干扰。 |
| 结果正确 | 结果错误 |
所以,tmp的作用就是充当一个“替身”,代替原始数据去完成那些会破坏数据本身的操作,从而保证主流程的正确执行。 这是一个非常基础和重要的编程技巧。
两个数组的交集

a. 将其中⼀个数组丢进哈希表中;
b. 遍历另⼀个数组的时候,在哈希表中看看就好了
代码如下:
lass Solution {
public:/*** 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可** * @param nums1 int整型vector * @param nums2 int整型vector * @return int整型vector*/bool hash[1010]={0};vector<int> intersection(vector<int>& nums1, vector<int>& nums2) {// write code herevector<int> ret;for(auto x:nums1){hash[x]=true;}for(auto x:nums2){if(hash[x]){ret.push_back(x);hash[x]=false;}}return ret;}
};
好的!这是一个非常关键的问题,它涉及到解决“数组交集”问题时一个常见的陷阱:如何避免结果中出现重复元素。
核心原因:防止重复添加相同的元素
hash[x] = false;放在 if (hash[x])条件内部,是为了在将一个元素添加到结果数组后,立即将其标记为“已使用”,从而确保即使该元素在第二个数组 nums2中出现多次,也只会被添加到结果中一次。
逐步拆解逻辑
我们结合你给出的示例2来分析,这样会非常清晰:
输入:nums1 = [1, 2, 3], nums2 = [8, 2, 2, 3, 8]
-
建立哈希表(第一阶段循环):
// 遍历 nums1,将出现的数字标记为 true for (int num : nums1) {hash[num] = true; }执行后,哈希表的状态是:
- •
hash[1] = true - •
hash[2] = true - •
hash[3] = true - •其他所有值都是
false
- •
-
查找交集并去重(第二阶段循环,关键步骤):
for (int x : nums2) { // 遍历 nums2: [8, 2, 2, 3, 8]if (hash[x]) { // 如果 x 在哈希表中为 true,说明它是交集元素result.push_back(x); // 将 x 加入结果hash[x] = false; // 【关键】立即将其标记为 false} }我们来一步步看循环过程:
- •
x = 8:hash[8]为false-> 不执行if条件内的语句 -> 跳过。 - •
x = 2:hash[2]为true-> 进入if条件内: •将2加入结果数组result。 •立即执行hash[2] = false;(标记为已使用)。 - •
x = 2:hash[2]现在已经是false了 -> 不进入if条件 -> 跳过。这样就避免了第二个2再次被加入结果。 - •
x = 3:hash[3]为true-> 进入if条件内: •将3加入结果数组result。 •立即执行hash[3] = false;。 - •
x = 8:hash[8]为false-> 跳过。
- •
-
3.最终结果: 结果数组
result为[2, 3],完美符合预期。
如果把这行代码移到 if 条件外面会怎样?
假设代码写成这样(错误的写法):
for (int x : nums2) {if (hash[x]) {result.push_back(x);}hash[x] = false; // 错误:移到了if外面
}
同样用示例2分析:
- •第一个
x=2满足条件,2被加入结果。然后hash[2]被设为false。 - •第二个
x=2不满足条件,被跳过。到这里还是正确的。 - •但是,当
x=8时,hash[8]原本是false,但依然会执行hash[8] = false;。这看起来没问题,但想象一下如果nums2里有数字1(它存在于nums1中,hash[1]为true),但这个1出现在第一个8之后: •在遇到1之前,先遇到了8,hash[8]被设为false(这没问题)。 •但hash[1]仍然为true,所以1会被正确加入结果。 •问题在于,这种写法进行了不必要的操作,逻辑意图不清晰。我们的目的很明确:只有当某个元素被成功添加为交集时,才需要去标记它以防重复。对于根本不在nums1里的元素(如8),我们不需要也不应该在这个循环里去修改它的哈希状态。把操作放在 if 内部,使得代码的“目的”和“执行”高度一致。
结论
hash[x] = false;放在 if (hash[x])条件内部,实现了 “使用即失效” 的机制。这是一个非常巧妙且高效的去重方法,它确保了:
- 1.每个公共元素只被添加到结果中一次。
- 2.代码逻辑清晰,只修改需要去重的元素的状态。
点击消除

代码如下:
#include <iostream>
#include <string>using namespace std;int main()
{string s,st;cin>>s;for(auto ch:s){if(st.size()&&st.back()==ch) st.pop_back();else st+=ch;}cout<<(st.size()==0?"0":st)<<endl;return 0;
}
1. for(auto ch : s)的含义
这是一种 C++ 中的范围 for 循环,也常被称为 foreach 循环。
- •作用:它用于按顺序遍历一个容器(比如这里的字符串
s)中的每一个元素。 - •执行过程: 1.从字符串
s的第一个字符开始。 2.每次循环,将当前字符赋值给变量ch。 3.执行循环体内的代码。 4.自动移动到下一个字符,直到遍历完整个字符串。
简单来说,for(auto ch : s)等价于下面这种传统的 for 循环:
for(int i = 0; i < s.size(); i++) {char ch = s[i]; // 取出字符串 s 中第 i 个位置的字符// ... 循环体 ...
}
但范围 for 循环的写法更简洁、更安全,不需要手动管理索引 i。
2. 整个代码的逻辑讲解
这段代码是实现“点击消除”问题的另一种经典且高效的方法。它没有使用标准的 stack库,而是直接用字符串 st来模拟栈的行为,非常巧妙。
#include <iostream>
#include <string>
using namespace std;int main() {string s, st; // s: 输入字符串, st: 用来模拟栈的字符串cin >> s;// 核心循环:遍历输入字符串 s 中的每一个字符 chfor(auto ch : s) {// 条件1: st.size() => 判断"栈"是否非空// 条件2: st.back() == ch => 判断"栈"顶元素(即st的最后一个字符)是否与当前字符ch相同if(st.size() && st.back() == ch) {// 如果相同,则发生消除:将"栈"顶字符弹出st.pop_back();} else {// 如果不相同(或栈为空),则将当前字符压入"栈"顶st += ch;}}// 输出结果:如果模拟栈st为空,输出"0",否则输出栈中剩余字符串cout << (st.size() == 0 ? "0" : st) << endl;return 0;
}
如何用字符串 st模拟栈?
- •
st的末尾(尾部)被视为栈顶。 - •入栈操作 (
st += ch):向字符串末尾添加一个字符,相当于将字符压入栈顶。 - •出栈操作 (
st.pop_back()):移除字符串的最后一个字符,相当于从栈顶弹出一个字符。 - •查看栈顶 (
st.back()):获取字符串的最后一个字符,相当于查看栈顶元素。 - •判断栈空 (
st.size()):检查字符串长度是否为0,相当于判断栈是否为空。
3. 举例说明
我们用一个例子来模拟,比如输入是 abbc:
当前字符 ch | 条件判断 (st非空且栈顶==ch?) | 操作 | 模拟栈 st的变化 |
|---|---|---|---|
a | 初始st为空 -> 条件不成立 | 执行 else:st += 'a' | st = "a" |
b | st非空,栈顶 'a'!= 'b'-> 条件不成立 | 执行 else:st += 'b' | st = "ab" |
b | st非空,栈顶 'b'== 'b'-> 条件成立 | 执行 if:st.pop_back() | st = "a"(消除了两个b) |
c | st非空,栈顶 'a'!= 'c'-> 条件不成立 | 执行 else:st += 'c' | st = "ac" |
循环结束,st不为空,输出 "ac"。
总结
- •
for(auto ch : s):是一种简洁的循环,用于遍历字符串s中的每个字符。 - •代码核心:使用字符串
st的末尾来模拟栈的后进先出特性,高效地解决了相邻字符消除的问题。 - •优点:这种方法比使用标准库的
stack更节省内存,因为最后可以直接输出字符串st,而无需像栈那样需要额外步骤来反转顺序。
st非空,栈顶 'a'!= 'c'-> 条件不成立 | 执行 else:st += 'c' | st = "ac" |
循环结束,st不为空,输出 "ac"。
总结
- •
for(auto ch : s):是一种简洁的循环,用于遍历字符串s中的每个字符。 - •代码核心:使用字符串
st的末尾来模拟栈的后进先出特性,高效地解决了相邻字符消除的问题。 - •优点:这种方法比使用标准库的
stack更节省内存,因为最后可以直接输出字符串st,而无需像栈那样需要额外步骤来反转顺序。
这种解法非常优雅和高效!
