【算法】基础篇

在这里插入图片描述

个人主页:C_GUIQU
归属专栏:算法

在这里插入图片描述

正文

1. 单位换算

1.1 长度单位换算

常见的长度单位有米(m)、厘米(cm)、毫米(mm)、千米(km)等,它们之间的换算关系如下:
1千米 = 1000米,1米 = 100厘米,1厘米 = 10毫米。例如,要将5千米换算成米,根据换算关系可得:5千米 = 5×1000 = 5000米。

1.2 重量单位换算

像千克(kg)、克(g)、吨(t)等是常用重量单位,换算关系为:1吨 = 1000千克,1千克 = 1000克。若要把3吨换算成克,计算就是3×1000×1000 = 3000000克。

1.3 时间单位换算

时间单位有秒(s)、分钟(min)、小时(h)等,1小时 = 60分钟,1分钟 = 60秒,1天 = 24小时。比如将2小时换算成秒,就是2×60×60 = 7200秒。

2. 日期问题

2.1 判断闰年

闰年的判断规则是:能被4整除但不能被100整除的年份为闰年,此外能被400整除的年份也是闰年。以下是C++代码示例来判断是否为闰年:

#include <iostream>
using namespace std;
bool isLeapYear(int year) {
    return (year % 4 == 0 && year % 100!= 0) || (year % 400 == 0);
}
int main() {
    int year;
    cout << "请输入年份: ";
    cin >> year;
    if (isLeapYear(year)) {
        cout << year << " 是闰年" << endl;
    } else {
        cout << year << " 不是闰年" << endl;
    }
    return 0;
}

2.2 计算两个日期之间的天数差

可以先把日期转化为从某个固定日期(比如公元元年1月1日)开始的天数,然后相减得到天数差。实现过程涉及到对年、月、日的分别处理以及考虑闰年等情况。

3. 编程语法

3.1 变量定义

在C++中,定义变量需要指定变量类型,例如:int num; 定义了一个整型变量numdouble price; 定义了一个双精度浮点型变量price等。不同类型的变量占用不同大小的内存空间,并且能存储的数据范围也不一样。

3.2 数据类型

常见的数据类型有整型(intlong long等)、浮点型(floatdouble)、字符型(char)、布尔型(bool)等。比如char ch = 'A'; 定义了一个字符型变量并赋值为字符A

3.3 控制结构

  • 顺序结构:代码按照书写的先后顺序依次执行,这是最基本的执行流程。
  • 选择结构:像if-else语句用于根据条件判断执行不同的代码块,示例如下:
int num = 10;
if (num > 5) {
    cout << "num大于5" << endl;
} else {
    cout << "num小于等于5" << endl;
}

还有switch语句用于多分支的情况,例如:

int day = 3;
switch (day) {
    case 1:
        cout << "星期一" << endl;
        break;
    case 2:
        cout << "星期二" << endl;
        break;
    case 3:
        cout << "星期三" << endl;
        break;
    // 可以继续添加更多的case分支
    default:
        cout << "其他" << endl;
}
  • 循环结构for循环常用于已知循环次数的情况,例如:
for (int i = 0; i < 10; i++) {
    cout << i << " ";
}

while循环用于在满足某个条件时持续执行循环体,例如:

int n = 0;
while (n < 5) {
    cout << n << " ";
    n++;
}

do-while循环则是先执行一次循环体再判断条件,示例:

int m = 0;
do {
    cout << m << " ";
    m++;
} while (m < 3);

4. 枚举(线性枚举、二分枚举、三分枚举)

4.1 线性枚举

线性枚举就是按照顺序依次遍历所有可能的情况。例如,要找出1到100中所有能被3整除的数,可以用以下代码实现:

for (int i = 1; i <= 100; i++) {
    if (i % 3 == 0) {
        cout << i << " ";
    }
}

4.2 二分枚举

二分枚举适用于有序数组中查找某个特定元素的情况。它基于二分查找的思想,每次把搜索区间缩小一半。以下是在一个有序数组arr中查找元素target的示例代码(假设数组arr已经升序排序):

#include <iostream>
using namespace std;
int binarySearch(int arr[], int n, int target) {
    int left = 0, right = n - 1;
    while (left <= right) {
        int mid = left + (right - left) / 2;
        if (arr[mid] == target) {
            return mid;
        } else if (arr[mid] < target) {
            left = mid + 1;
        } else {
            right = mid - 1;
        }
    }
    return -1;  // 表示没找到
}
int main() {
    int arr[] = {1, 3, 5, 7, 9};
    int n = sizeof(arr) / sizeof(arr[0]);
    int target = 5;
    int result = binarySearch(arr, n, target);
    if (result!= -1) {
        cout << "找到元素,下标为: " << result << endl;
    } else {
        cout << "未找到元素" << endl;
    }
    return 0;
}

4.3 三分枚举

三分枚举常用于求解单峰函数的极值问题。例如对于一个函数f(x),它在某个区间内先单调递增然后单调递减(或者先单调递减然后单调递增),通过不断缩小区间来逼近极值点。代码实现大致思路如下(这里只是简单示意,实际函数根据具体情况而定):

double f(double x) {
    // 这里定义具体的函数表达式,比如返回 x * x - 4 * x + 3; 等
    return x * x - 4 * x + 3;
}
double ternarySearch(double left, double right) {
    const double eps = 1e-9;  // 精度要求
    while (right - left > eps) {
        double mid1 = left + (right - left) / 3;
        double mid2 = right - (right - left) / 3;
        if (f(mid1) < f(mid2)) {
            right = mid2;
        } else {
            left = mid1;
        }
    }
    return (left + right) / 2;
}

5. 模拟

模拟就是按照题目给定的规则和逻辑,一步一步地去实现相应的过程。比如模拟电梯的运行过程,要考虑电梯的当前楼层、上下行状态、乘客的进出等情况。以下是一个简单的模拟时钟走时的代码示例,模拟秒针、分针、时针的转动:

#include <iostream>
using namespace std;
struct Clock {
    int hour;
    int minute;
    int second;
};
void tick(Clock& c) {
    c.second++;
    if (c.second == 60) {
        c.second = 0;
        c.minute++;
        if (c.minute == 60) {
            c.minute = 0;
            c.hour++;
            if (c.hour == 24) {
                c.hour = 0;
            }
        }
    }
}
int main() {
    Clock c = {0, 0, 0};
    for (int i = 0; i < 10; i++) {  // 模拟走10秒
        tick(c);
        cout << c.hour << ":" << c.minute << ":" << c.second << endl;
    }
    return 0;
}

6. 离散化

离散化主要用于处理数据值域很大但实际有效数据个数相对较少的情况,将数据映射到一个相对小的区间内。例如,有一组数据表示一些点在数轴上的坐标,坐标值范围很大(比如从-10000到10000),但点的个数只有10个,就可以通过离散化把这些坐标值重新映射到0到9这样的小范围内。代码实现大致如下(这里是简单示意,假设数据已经存储在数组nums中):

#include <algorithm>
#include <iostream>
#include <vector>
using namespace std;
vector<int> discretize(vector<int> nums) {
    vector<int> sortedNums = nums;
    sort(sortedNums.begin(), sortedNums.end());
    sortedNums.erase(unique(sortedNums.begin(), sortedNums.end()), sortedNums.end());
    vector<int> result;
    for (int num : nums) {
        result.push_back(lower_bound(sortedNums.begin(), sortedNums.end(), num) - sortedNums.begin());
    }
    return result;
}
int main() {
    vector<int> nums = {5, 3, 7, 3, 9};
    vector<int> discreteNums = discretize(nums);
    for (int num : discreteNums) {
        cout << num << " ";
    }
    return 0;
}

7. 前缀和

前缀和是一种预处理技巧,用于快速计算数组中某一段区间的元素和。例如对于数组arr,定义前缀和数组preSum,其中preSum[i]表示数组arr中从第0个元素到第i个元素的和。代码如下:

#include <iostream>
#include <vector>
using namespace std;
vector<int> prefixSum(vector<int> arr) {
    int n = arr.size();
    vector<int> preSum(n);
    preSum[0] = arr[0];
    for (int i = 1; i < n; i++) {
        preSum[i] = preSum[i - 1] + arr[i];
    }
    return preSum;
}
int main() {
    vector<int> arr = {1, 2, 3, 4, 5};
    vector<int> preSum = prefixSum(arr);
    // 要计算区间 [1, 3](下标从0开始,实际是第2个到第4个元素)的和
    int sum = preSum[3] - preSum[1 - 1];
    cout << "区间和为: " << sum << endl;
    return 0;
}

8. 差分

差分是前缀和的逆运算,常用于对数组的区间修改操作,然后通过前缀和还原出修改后的数组。例如,要对数组arr的某个区间[l, r](下标从0开始)中的每个元素都加上一个值val,可以先构建差分数组diff,然后进行相应操作,最后通过前缀和得到修改后的数组。代码示例:

#include <iostream>
#include <vector>
using namespace std;
vector<int> difference(vector<int> arr) {
    int n = arr.size();
    vector<int> diff(n);
    diff[0] = arr[0];
    for (int i = 1; i < n; i++) {
        diff[i] = arr[i] - arr[i - 1];
    }
    return diff;
}
void addRange(vector<int>& diff, int l, int r, int val) {
    diff[l] += val;
    if (r + 1 < diff.size()) {
        diff[r + 1] -= val;
    }
}
vector<int> restoreArray(vector<int>& diff) {
    int n = diff.size();
    vector<int> arr(n);
    arr[0] = diff[0];
    for (int i = 1; i < n; i++) {
        arr[i] = arr[i - 1] + diff[i];
    }
    return arr;
}
int main() {
    vector<int> arr = {1, 3, 5, 7, 9};
    vector<int> diff = difference(arr);
    addRange(diff, 1, 3, 2);  // 对区间 [1, 3](下标从0开始)的元素都加2
    vector<int> newArr = restoreArray(diff);
    for (int num : newArr) {
        cout << num << " ";
    }
    return 0;
}

9. 二分

二分查找前面在枚举部分已经举例过了,这里再强调下其核心思想是不断缩小搜索区间,每次根据中间元素与目标元素的大小关系来决定是向左还是向右继续查找,它的时间复杂度是 O ( l o g n ) O(log n) O(logn),在有序数据查找场景中效率很高。

10. 进制转换

10.1 十进制转二进制

可以用除2取余的方法,将十进制数不断除以2,记录余数,直到商为0,然后将余数从下往上排列就是对应的二进制数。以下是C++代码实现:

#include <iostream>
#include <stack>
using namespace std;
string decimalToBinary(int decimal) {
    stack<int> s;
    while (decimal > 0) {
        s.push(decimal % 2);
        decimal /= 2;
    }
    string binary = "";
    while (!s.empty()) {
        binary += to_string(s.top());
        s.pop();
    }
    return binary;
}
int main() {
    int decimal = 10;
    string binary = decimalToBinary(decimal);
    cout << decimal << "的二进制表示为: " << binary << endl;
    return 0;
}

10.2 二进制转十进制

将二进制数的每一位乘以2的相应幂次(幂次从0开始,从右往左数)然后相加即可得到十进制数。例如二进制数1010,转换过程为:(1×2³ + 0×2² + 1×2¹ + 0×2⁰) = 10

10.3 其他进制转换

类似的思路可以推广到十进制与其他进制(如八进制、十六进制等)之间的转换,只是除数或者幂次的底数相应改变,并且十六进制涉及到用字母表示大于9的数字(如A表示10,B表示11等)。

11. 贪心

贪心算法是一种在每一步选择中都采取当前状态下的最优决策,期望最终能得到全局最优解的算法策略。例如找零钱问题,假设有不同面额的硬币{1, 5, 10, 25},要凑出给定金额(比如30),可以每次优先选择面额大的硬币,直到凑够金额或者无法再选择为止。代码示例:

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
vector<int> coins = {25, 10, 5, 1};
int makeChange(int amount) {
    int count = 0;
    for (int coin : coins) {
        count += amount / coin;
        amount %= coin;
    }
    return count;
}
int main() {
    int amount = 30;
    int result = makeChange(amount);
    cout << "凑出 " << amount << " 需要的硬币个数为: " << result << endl;
    return 0;
}

但要注意,贪心算法并不总是能保证得到全局最优解,需要满足一定的条件(比如具有最优子结构性质等),有些情况下需要用动态规划等其他算法来验证或求解。

12. 位运算

12.1 按位与(&

按位与运算是对两个整数的二进制表示的每一位进行与操作,只有当对应的两位都为1时,结果位才为1,否则为0。例如,3 & 53的二进制是00115的二进制是0101,按位与的结果是0001,也就是十进制的1。

12.2 按位或(|

按位或运算对两个整数二进制表示的每一位进行或操作,只要对应的两位中有一位为1,结果位就为1。例如,3(二进制为0011)和5(二进制为0101)进行按位或运算,得到的结果是0111,也就是十进制的7。代码示例如下:

#include <iostream>
using namespace std;
int main() {
    int num1 = 3, num2 = 5;
    int result = num1 | num2;
    cout << "3和5按位或的结果为: " << result << endl;
    return 0;
}

12.3 按位异或(^

按位异或运算对两个整数二进制表示的每一位进行异或操作,当对应的两位不同时(一个为0,一个为1),结果位为1,两位相同时结果位为0。比如,3(二进制0011)和5(二进制0101)按位异或,结果为0110,即十进制的6。示例代码:

#include <iostream>
using namespace std;
int main() {
    int num1 = 3, num2 = 5;
    int result = num1 ^ num2;
    cout << "3和5按位异或的结果为: " << result << endl;
    return 0;
}

12.4 取反(~

取反运算对一个整数的二进制表示的每一位进行取反操作,0变为1,1变为0。需要注意的是,在C++等语言中,取反操作的结果是按照补码形式存储的,且最终呈现出来的是补码对应的十进制数。例如,对整数3(二进制0011,假设是8位表示,即00000011)取反后得到11111100(补码),对应的十进制数为-4(原码为10000100)。示例代码:

#include <iostream>
using namespace std;
int main() {
    int num = 3;
    int result = ~num;
    cout << "3取反的结果为: " << result << endl;
    return 0;
}

12.5 左移(<<

左移运算将一个整数的二进制表示向左移动指定的位数,右边空出的位用0填充。左移一位相当于乘以2,左移n位相当于乘以2^n。例如,3(二进制0011)左移2位后变为1100,也就是十进制的12。代码示例:

#include <iostream>
using namespace std;
int main() {
    int num = 3;
    int result = num << 2;
    cout << "3左移2位的结果为: " << result << endl;
    return 0;
}

12.6 右移(>>

右移运算将一个整数的二进制表示向右移动指定的位数,对于无符号整数,左边空出的位用0填充;对于有符号整数,如果是算术右移(大多数情况下是这样),左边空出的位用符号位填充(正数用0,负数用1)。右移一位相当于除以2(向下取整),右移n位相当于除以2^n(向下取整)。例如,12(二进制1100)右移2位后变为0011,也就是十进制的3。示例代码:

#include <iostream>
using namespace std;
int main() {
    int num = 12;
    int result = num >> 2;
    cout << "12右移2位的结果为: " << result << endl;
    return 0;
}

13. 双指针

13.1 快慢双指针

常用于解决链表、数组等相关问题,比如判断链表中是否存在环,可以用快慢指针同时遍历链表,快指针每次移动两步,慢指针每次移动一步,如果快慢指针相遇了,那就说明链表存在环。以下是判断链表是否有环的C++代码示例(这里先定义简单的链表结构体):

#include <iostream>
using namespace std;
// 定义链表节点结构体
struct ListNode {
    int val;
    ListNode* next;
    ListNode(int x) : val(x), next(NULL) {}
};
bool hasCycle(ListNode* head) {
    if (head == NULL) return false;
    ListNode* slow = head;
    ListNode* fast = head;
    while (fast!= NULL && fast->next!= NULL) {
        slow = slow->next;
        fast = fast->next->next;
        if (slow == fast) return true;
    }
    return false;
}
int main() {
    // 构建一个简单有环链表示例(这里手动构建简单情况演示)
    ListNode* head = new ListNode(1);
    ListNode* node2 = new ListNode(2);
    ListNode* node3 = new ListNode(3);
    ListNode* node4 = new ListNode(4);
    ListNode* node5 = new ListNode(5);
    ListNode* node6 = new ListNode(6);
    head->next = node2;
    node2->next = node3;
    node3->next = node4;
    node4->next = node5;
    node5->next = node6;
    node6->next = node3;  // 构成环
    bool result = hasCycle(head);
    if (result) {
        cout << "链表存在环" << endl;
    } else {
        cout << "链表不存在环" << endl;
    }
    return 0;
}

13.2 对撞双指针

常用于在有序数组中找满足一定条件的两个元素,比如在一个有序数组中找两数之和等于给定目标值的两个数。可以让一个指针从数组开头,一个指针从数组末尾,根据两数之和与目标值的大小关系移动指针。示例代码如下(假设数组已经有序):

#include <iostream>
#include <vector>
using namespace std;
vector<int> twoSum(vector<int> nums, int target) {
    int left = 0, right = nums.size() - 1;
    while (left < right) {
        int sum = nums[left] + nums[right];
        if (sum == target) {
            return {nums[left], nums[right]};
        } else if (sum < target) {
            left++;
        } else {
            right--;
        }
    }
    return {};  // 如果没找到合适的就返回空向量
}
int main() {
    vector<int> nums = {1, 3, 4, 6, 8, 10};
    int target = 9;
    vector<int> result = twoSum(nums, target);
    if (result.size() > 0) {
        cout << "找到的两个数为: " << result[0] << " 和 " << result[1] << endl;
    } else {
        cout << "未找到满足条件的两个数" << endl;
    }
    return 0;
}

结语
感谢您的阅读!期待您的一键三连!欢迎指正!

在这里插入图片描述

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Guiat

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值