2026.1.19学习笔记

学习1:c++

今日学习了数组 函数 指针三个基础板块

数组

在学习数组时学习了一维数组 二维数组,其中包含例题

例题1:统计总成绩

例题2:冒泡排序

算法题的基础,学会二重循环

例题3:交换排序swap

例题4:数组逆置

此处是重点,可着重掌握,包括链表逆置方法也可以扩展学习

例题5:五只小猪称体重

相当于使用max函数

复制代码
//int zongchengji() {
//	//统计每个人的总和分数
//
//	int scores[3][3] =
//	{
//		{100,100,100},
//		{90,50,100},
//		{60,70,80},
//	};
//	string names[3] = {"张三", "李四", "王五"};
//
//	for (int i = 0; i < 3; i++)
//	{
//		int sum = 0;
//		for (int j = 0; j < 3; j++) {
//			sum += scores[i][j];
//			//cout << scores[i][j] << " ";
//		}
//		cout << names[i] << "个人的总分为: " << sum << endl;
//	}
//
//
//}

//int  erweishuzujichu() {
//	//二维数组
//	int arr[2][3] = { {1,2,3}, {4,5,6} };
//	//查看占用空间大小
//	cout << "二维数组元素占用空间大小为:" << sizeof(arr) << endl;
//	cout << "二维数组第一行占用空间大小为:" << sizeof(arr[0]) << endl;
//	cout << "二维数组第一个数据占用空间大小为:" << sizeof(arr[0][0]) << endl;
//
//	cout << "二维数组行数为:" << sizeof(arr) / sizeof(arr[0]) << endl;
//	cout << "二维数组列数为:" << sizeof(arr[0]) / sizeof(arr[0][0]) << endl;
//
//
//
//}

//int maopaopaixu() {
//	int arr[9] = { 4,2,8,0,5,7,1,3,9 };
//
//	cout << "排序前:" << endl;
//	for (int i = 0; i < 9; i++)
//	{
//		cout << arr[i] << endl;
//	}
//
//	//总共排序轮数为 元素个数-1
//	for (int i = 0; i < 9-1; i++)
//	{
//		//内层循环对比 次数= 元素个数 - 当前轮数 -1
//		for (int j = 0; j < 9-i-1; j++)
//		{
//			//如果第一个数字比第二个数字大,交换俩个数字
//			if (arr[j] > arr[j + 1]){
//				swap(arr[j], arr[j + 1]);
//			}
//
//		}
//	}
//
//	cout << "排序后:" << endl;
//	for (int i = 0; i < 9; i++)
//	{
//		cout << arr[i] << endl;
//	}
//}
//
//void swap(int& a, int& b) {
//	int temp = a;
//	a = b;
//	b = temp;
//}
//int reverse() {
//	//数组逆置
//	int arr[5] = { 4,2,8,0,5 };
//
//	cout << "数组元素逆置前:" << endl;
//	for (int i = 0; i < 5; i++)
//	{
//		cout << arr[i] << endl;
//	}
//
//	int start = 0; //找到起始元素下标
//	int end = sizeof(arr) / sizeof(arr[0]) - 1; //找到末尾元素下标
//
//	//需要创建临时变量保存第一个数据
//	while (start<end)
//	{
//		int temp = arr[start];
//		arr[start] = arr[end];
//		arr[end] = temp;
//		//下标更新
//		start++;
//		end--;
//	}
//	//打印逆置后的数组
//	cout << "数组元素逆置后:" << endl;
//	for (int i = 0; i < 5; i++)
//	{
//		cout << arr[i] << endl;
//	}
//}

//int findmaxheavy() {
//	//五只小猪称体重
//	int arr[5] = { 300,350,200,400,250 };
//	int max = 0;
//	for (int i = 0; i < 5; i++)
//	{
//		if (max < arr[i]) {
//			max = arr[i];
//		}
//	}
//	cout<< "最重的小猪体重为:" << max << endl;
//}

//int shuzujibenyongfa() {
//	
//	//通过数组名统计整个数组占用内存大小
//	int arr[5] = { 1, 2, 3, 4, 5 };
//	cout << "整个数组空间占用内存空间为:" << sizeof(arr) << endl;
//	cout << "每个元素空间占用内存空间为:" << sizeof(arr[0]) << endl;
//	cout << "数组中元素个数为:" << sizeof(arr)/sizeof(arr[0]) << endl;
//
//	//通过数组名查看数组首地址
//	cout << "数组首地址为" << (int)arr << endl;
//	cout << "数组中第二个元素地址为:" << (int)&arr[1] << endl;
//}

函数

重点学习了函数的定义 声明 调用和分文件编写

其中需要着重注意的如下

函数的声明

作用: 告诉编译器函数名称及如何调用函数。函数的实际主体可以单独定义。

  • 函数的声明可以多次 ,但是函数的定义只能有一次

示例:

复制代码
//声明可以多次,定义只能一次
//声明
int max(int a, int b);
int max(int a, int b);
//定义
int max(int a, int b)
{
	return a > b ? a : b;
}

int main() {

	int a = 100;
	int b = 200;

	cout << max(a, b) << endl;

	system("pause");

	return 0;
}

函数的分文件编写

**作用:**让代码结构更加清晰

函数分文件编写一般有4个步骤

  1. 创建后缀名为.h的头文件
  2. 创建后缀名为.cpp的源文件
  3. 在头文件中写函数的声明
  4. 在源文件中写函数的定义

示例:

复制代码
//swap.h文件
#include<iostream>
using namespace std;

//实现两个数字交换的函数声明
void swap(int a, int b);

//swap.cpp文件
#include "swap.h"

void swap(int a, int b)
{
	int temp = a;
	a = b;
	b = temp;

	cout << "a = " << a << endl;
	cout << "b = " << b << endl;
}

//main函数文件
#include "swap.h"
int main() {

	int a = 100;
	int b = 200;
	swap(a, b);

	system("pause");

	return 0;
}

如下所示

在头文件中使用声明,在源文件中使用定义

同时在源文件中引用是需要#include xxx.h

指针

学习了指针的基本概念 定义 所占内存空间 const修饰指针 指针和数组 指针和函数 指针,数组和函数

其实重要的点如下

const修饰指针

const修饰指针有三种情况

  1. const修饰指针 --- 常量指针
  2. const修饰常量 --- 指针常量
  3. const即修饰指针,又修饰常量

示例:

复制代码
int main() {

	int a = 10;
	int b = 10;

	//const修饰的是指针,指针指向可以改,指针指向的值不可以更改
	const int * p1 = &a; 
	p1 = &b; //正确
	//*p1 = 100;  报错
	

	//const修饰的是常量,指针指向不可以改,指针指向的值可以更改
	int * const p2 = &a;
	//p2 = &b; //错误
	*p2 = 100; //正确

    //const既修饰指针又修饰常量
	const int * const p3 = &a;
	//p3 = &b; //错误
	//*p3 = 100; //错误

	system("pause");

	return 0;
}

技巧:看const右侧紧跟着的是指针还是常量, 是指针就是常量指针,是常量就是指针常量

指针、数组、函数

**案例描述:**封装一个函数,利用冒泡排序,实现对整型数组的升序排序

例如数组:int arr[10] = { 4,3,6,9,1,2,10,8,7,5 };

示例:

复制代码
//冒泡排序函数
void bubbleSort(int * arr, int len)  //int * arr 也可以写为int arr[]
{
	for (int i = 0; i < len - 1; i++)
	{
		for (int j = 0; j < len - 1 - i; j++)
		{
			if (arr[j] > arr[j + 1])
			{
				int temp = arr[j];
				arr[j] = arr[j + 1];
				arr[j + 1] = temp;
			}
		}
	}
}

//打印数组函数
void printArray(int arr[], int len)
{
	for (int i = 0; i < len; i++)
	{
		cout << arr[i] << endl;
	}
}

int main() {

	int arr[10] = { 4,3,6,9,1,2,10,8,7,5 };
	int len = sizeof(arr) / sizeof(int);

	bubbleSort(arr, len);

	printArray(arr, len);

	system("pause");

	return 0;
}

总结:当数组名传入到函数作为参数时,被退化为指向首元素的指针

代码编程题

27. 移除元素 - 力扣(LeetCode)

同时观看了代码随想录第二期视频:https://www.bilibili.com/video/BV12A4y1Z7LP?t=0.1

在本期视频中学习了如何删除数组元素,由于数组是一个连续的集合,本质上删除元素就是由后面的元素 替换被删除的元素即可,同时做了力扣27题:27. 移除元素 - 力扣(LeetCode)

代码如下

复制代码
class Solution {
public:
    int removeElement(vector<int>& nums, int val) {
        int slow=0;
        for(int fast = 0; fast<nums.size();fast++){
            if(nums[fast] != val){
                nums[slow++] = nums[fast];
            }
        }
        return slow;
    }
};

其中着重看for循环中的内容,理解替换的含义

160. 相交链表 - 力扣(LeetCode)

做了hot第一题:160. 相交链表 - 力扣(LeetCode)

该题在408中有涉及,本人第一个思路是俩个指针分别指向俩个链表首指针,比较指针长度,当lenA>lenB时候,pA = pA ->next;同理,当lenA= lenB时候,如果pA ->val != pB -> val则同时向后移动,但实行起来时候困难重重,不得不借助豆包写出如下代码

复制代码
/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
        // 边界判断:任意一个链表为空,直接返回空(无相交节点)
        if(headA == NULL || headB == NULL) return NULL;
        
        // 1. 定义两个指针,初始指向两个链表的头节点(修正语法错误)
        ListNode* pa = headA;
        ListNode* pb = headB;
        
        // 2. 计算链表A的总长度 lenA
        int lenA = 0;
        while(pa != NULL){
            lenA++;
            pa = pa->next;
        }
        // 3. 计算链表B的总长度 lenB
        int lenB = 0;
        while(pb != NULL){
            lenB++;
            pb = pb->next;
        }
        
        // 4. 重置指针到表头,准备对齐长度
        pa = headA;
        pb = headB;
        
        // 5. 让长链表的指针先走「长度差」步,对齐两个指针的起点(核心修正)
        int gap = abs(lenA - lenB);
        if(lenA > lenB){
            while(gap--){
                pa = pa->next;
            }
        }else{
            while(gap--){
                pb = pb->next;
            }
        }
        
        // 6. 同步遍历两个链表,找相交节点(核心修正:判断指针相等,而非值相等)
        while(pa != NULL && pb != NULL){
            if(pa == pb){ // 找到相交节点,直接返回
                return pa;
            }
            pa = pa->next;
            pb = pb->next;
        }
        
        // 7. 遍历结束都没找到,说明无相交节点,返回空
        return NULL;
    }
};

其中遇到的经典错误可以查看如下对话https://www.doubao.com/thread/w626affc0e961d716

同时使用评论区较为简单方法

复制代码
if(headA == NULL || headB == NULL) return NULL;
        ListNode* pA = headA;
        ListNode* pB = headB;
        
        while(pA != pB) {
            pA = pA == NULL ? headB : pA->next;
            pB = pB == NULL ? headA : pB->next;
        }
        return pA;
    }

其中重点部分为三目运算符中的内容,详细解释如下

一、先补基础:C++ 三目运算符 ?: 完整语法规则

这段代码的核心就是两个三目运算符 ,它是你理解这段逻辑的前提,三目运算符也叫「三元表达式」,是 if-else 条件判断的极简写法,语法规则非常固定:

✅ 标准格式

复制代码
条件表达式 ? 表达式A : 表达式B;

✅ 执行逻辑(必须记住)

  1. 先判断「条件表达式」的结果是 true 还是 false

  2. 如果条件为 true → 整个三目表达式的结果 = 表达式 A

  3. 如果条件为 false → 整个三目表达式的结果 = 表达式 B

  4. 三目运算符的优先级低于赋值号=,所以你的代码里是「先算三目表达式的结果,再赋值给左边的变量」。

二、逐行解析:你的两行三目运算符代码 「直译 + 执行逻辑」

先把核心代码再贴一遍,方便对照:

复制代码
while (pa != pb) {
    pa = pa == nullptr ? headB : pa->next;
    pb = pb == nullptr ? headA : pb->next;
}

已知前提:papb 都是 ListNode* 类型的指针变量headA是链表 A 的头指针,headB是链表 B 的头指针,pa->next表示指针pa指向的节点的下一个节点,pb->next同理。

✅ 第一行:pa = pa == nullptr ? headB : pa->next;

✔️ 直译(人话版本)

给指针变量pa 重新赋值:如果 pa 这个指针已经走到链表末尾(变成空指针)了 → 就把pa 指向「链表 B 的头节点headB」;如果 pa 这个指针还没走到末尾(不是空指针) → 就把pa 往后移动一步,指向当前节点的下一个节点。

✔️ 拆解成条件判断

复制代码
// 这行 if-else 等价于上面的三目运算符,逻辑完全一样,只是写法更啰嗦
if (pa == nullptr) {
    pa = headB;
} else {
    pa = pa->next;
}

✅ 第二行:pb = pb == nullptr ? headA : pb->next;

✔️ 直译(人话版本)

给指针变量pb 重新赋值:如果 pb 这个指针已经走到链表末尾(变成空指针)了 → 就把pb 指向「链表 A 的头节点headA」;如果 pb 这个指针还没走到末尾(不是空指针) → 就把pb 往后移动一步,指向当前节点的下一个节点。

✔️ 拆解成条件判断

复制代码
// 等价的if-else写法,逻辑完全一致
if (pb == nullptr) {
    pb = headA;
} else {
    pb = pb->next;
}

三、✅ 整个 while + 三目 完整执行规则(重中之重)

把三目和外层的while(pa != pb)结合起来,这段代码的完整执行逻辑就是:

  1. 初始化:pa指向链表 A 的头节点,pb指向链表 B 的头节点;

  2. 进入循环的条件:papb 指向的不是同一个节点

  3. 循环内同步操作两个指针:

    • pa 走到底了,就「瞬移」到链表 B 的开头继续走;没走到底,就正常往后走一步;

    • pb 走到底了,就「瞬移」到链表 A 的开头继续走;没走到底,就正常往后走一步;

  4. 什么时候退出循环? → 当 pa == pb 时,也就是两个指针指向了同一个节点

  5. 退出循环后,直接返回pa(或pb)即可,这就是我们要找的「相交节点」。

补充:pa == pb 成立的两种情况(完美处理所有边界)

这个逻辑的强大之处在于,能自动处理「链表相交」和「链表不相交」两种场景,不会崩溃、不用额外判断,这也是它能成为最优解的核心原因:

  1. 情况 1:两个链表有相交节点papb会在相交节点的位置 满足 pa == pb,退出循环,返回该节点 ✔️;

  2. 情况 2:两个链表没有相交节点papb会一直走,最终都走到空指针(nullptr) ,此时 pa == pb == nullptr,退出循环,返回nullptr ✔️。


四、✅ 为什么这么写就能精准找到「相交节点」?(底层核心原理,彻底懂透)

你之前自己写代码的时候,思路是「先计算两个链表的长度差,让长链表的指针先走几步,对齐长度后再同步遍历」,这个思路是正确的 ,而这段双指针 + 三目运算符的代码,本质上是 「用代码逻辑自动抵消长度差,省去手动计算长度的步骤」,二者的底层原理完全一致,只是实现方式不同。

我用通俗的语言解释这个核心原理,举个例子你就秒懂:假设:链表 A 的长度是 lenA,链表 B 的长度是 lenB

  • pa 指针的行走路径:先走完链表 A 的lenA个节点 → 走到头后瞬移到链表 B 开头 → 再走lenB个节点;总路程 = lenA + lenB

  • pb 指针的行走路径:先走完链表 B 的lenB个节点 → 走到头后瞬移到链表 A 开头 → 再走lenA个节点;总路程 = lenB + lenA

核心结论:两个指针的「总行走路程完全相等」!不管两个链表的长度差是多少,只要有相交节点,两个指针一定会在「相交节点」相遇;如果没有相交节点,两个指针会同时走到末尾的空指针,完美对齐。

举个通俗的例子:你和你的朋友,从两条不同长度的路出发去同一个目的地,你走到头就换对方的路走,对方走到头就换你的路走,你们的速度一样,总路程一样,最终一定会在目的地相遇(或者同时走到终点)。

五、✅ 等价代码:把整段逻辑改成「纯 if-else」写法(新手友好,零理解难度)

如果你现在对三目运算符还是觉得别扭,没关系,我把这段核心逻辑完整转换成等价的 if-else 写法,代码功能、执行效率、结果完全一致,只是写法更啰嗦,但对新手更友好,你可以对照着看,彻底理解:

复制代码
// 等价的纯if-else写法,和你的三目运算符版本完全一样
while (pa != pb) {
    // 处理pa指针
    if (pa == nullptr) {
        pa = headB;
    } else {
        pa = pa->next;
    }
    // 处理pb指针
    if (pb == nullptr) {
        pb = headA;
    } else {
        pb = pb->next;
    }
}

六、✅ 补充 2 个关键注意点(避坑 + 语法巩固)

  1. 为什么指针访问成员用 -> 而不是 .

你之前的代码里犯过这个语法错误,这里再巩固一次,永远记住:

  • paListNode* 类型 → 指针类型 → 访问成员(val/next)必须用 -> ,比如 pa->next

  • 如果是 ListNode node 这种实体对象 → 访问成员才用 . ,比如 node.next

  1. 这个解法的效率有多高?(刷题必看)
  • 时间复杂度:O(lenA + lenB) → 每个指针最多走「两个链表总长度」的步数,只遍历一次;

  • 空间复杂度:O(1) → 只定义了两个指针变量,没有开辟任何额外的内存空间;这是这道题的 最优解,没有比这个更好的解法了,面试写这个代码,面试官一定会认可👍。


✅ 总结(精华提炼,直接记)

  1. 三目运算符 条件?A:B → 条件为真执行 A,为假执行 B,是if-else的简写;

  2. 核心逻辑一句话:谁走到头,谁就瞬移到另一个链表的头节点,直到两个指针相遇

  3. 相遇的结果只有两种:要么是「相交节点」,要么是「空指针(无相交)」;

  4. 这个写法的精髓:不用手动算链表长度,自动抵消长度差,代码极简、效率拉满

相关推荐
zfj3212 小时前
小数和整数10进制转2进制算法
算法·二进制·进制转换·十进制
Tisfy2 小时前
LeetCode 3314.构造最小位运算数组 I:今日先简单题简单做-到II再优化
算法·leetcode·题解·模拟·数组
鄭郑2 小时前
【Wordpress笔记02】文章分类与标签
笔记
AlenTech2 小时前
595. 大的国家 - 力扣(LeetCode)
算法·leetcode·职场和发展
橘颂TA2 小时前
【剑斩OFFER】算法的暴力美学——力扣 433 题:最小基因变化
数据结构·c++·算法·哈希算法
橘颂TA2 小时前
【剑斩OFFER】算法的暴力美学——力扣 1926 题:迷宫中离入口最近的出口
c++·算法·结构与算法
iAkuya2 小时前
(leetcode)力扣100 47从前序与中序遍历序列构造二叉树(迭代,递归)
算法·leetcode·职场和发展
smj2302_796826522 小时前
解决leetcode第3816题.删除重复字符后的字典序最小字符串
python·算法·leetcode
十八岁讨厌编程2 小时前
【算法训练营 · 二刷总结篇】 数组与字符串部分
算法