Merge branch 'youngyangyang04:master' into master

This commit is contained in:
Arthur Pan
2022-05-14 16:26:12 +01:00
committed by GitHub
20 changed files with 499 additions and 85 deletions

View File

@ -313,54 +313,36 @@ func threeSum(nums []int)[][]int{
javaScript:
```js
/**
* @param {number[]} nums
* @return {number[][]}
*/
// 循环内不考虑去重
var threeSum = function(nums) {
const len = nums.length;
if(len < 3) return [];
nums.sort((a, b) => a - b);
const resSet = new Set();
for(let i = 0; i < len - 2; i++) {
if(nums[i] > 0) break;
let l = i + 1, r = len - 1;
const res = [], len = nums.length
// 将数组排序
nums.sort((a, b) => a - b)
for (let i = 0; i < len; i++) {
let l = i + 1, r = len - 1, iNum = nums[i]
// 数组排过序如果第一个数大于0直接返回res
if (iNum > 0) return res
// 去重
if (iNum == nums[i - 1]) continue
while(l < r) {
const sum = nums[i] + nums[l] + nums[r];
if(sum < 0) { l++; continue };
if(sum > 0) { r--; continue };
resSet.add(`${nums[i]},${nums[l]},${nums[r]}`);
l++;
r--;
let lNum = nums[l], rNum = nums[r], threeSum = iNum + lNum + rNum
// 三数之和小于0则左指针向右移动
if (threeSum < 0) l++
else if (threeSum > 0) r--
else {
res.push([iNum, lNum, rNum])
// 去重
while(l < r && nums[l] == nums[l + 1]){
l++
}
while(l < r && nums[r] == nums[r - 1]) {
r--
}
l++
r--
}
}
}
return Array.from(resSet).map(i => i.split(","));
};
// 去重优化
var threeSum = function(nums) {
const len = nums.length;
if(len < 3) return [];
nums.sort((a, b) => a - b);
const res = [];
for(let i = 0; i < len - 2; i++) {
if(nums[i] > 0) break;
// a去重
if(i > 0 && nums[i] === nums[i - 1]) continue;
let l = i + 1, r = len - 1;
while(l < r) {
const sum = nums[i] + nums[l] + nums[r];
if(sum < 0) { l++; continue };
if(sum > 0) { r--; continue };
res.push([nums[i], nums[l], nums[r]])
// b c 去重
while(l < r && nums[l] === nums[++l]);
while(l < r && nums[r] === nums[--r]);
}
}
return res;
return res
};
```
TypeScript:

View File

@ -31,7 +31,7 @@
四数之和,和[15.三数之和](https://programmercarl.com/0015.三数之和.html)是一个思路,都是使用双指针法, 基本解法就是在[15.三数之和](https://programmercarl.com/0015.三数之和.html) 的基础上再套一层for循环。
但是有一些细节需要注意,例如: 不要判断`nums[k] > target` 就返回了,三数之和 可以通过 `nums[i] > 0` 就返回了,因为 0 已经是确定的数了,四数之和这道题目 target是任意值。(大家亲自写代码就能感受出来)
但是有一些细节需要注意,例如: 不要判断`nums[k] > target` 就返回了,三数之和 可以通过 `nums[i] > 0` 就返回了,因为 0 已经是确定的数了,四数之和这道题目 target是任意值。比如:数组是`[-4, -3, -2, -1]``target``-10`,不能因为`-4 > -10`而跳过。但是我们依旧可以去做剪枝,逻辑变成`nums[i] > target && (nums[i] >=0 || target >= 0)`就可以了。
[15.三数之和](https://programmercarl.com/0015.三数之和.html)的双指针解法是一层for循环num[i]为确定值然后循环内有left和right下标作为双指针找到nums[i] + nums[left] + nums[right] == 0。
@ -72,15 +72,20 @@ public:
vector<vector<int>> result;
sort(nums.begin(), nums.end());
for (int k = 0; k < nums.size(); k++) {
// 这种剪枝是错误的这道题目target 是任意值
// if (nums[k] > target) {
// return result;
// }
// 剪枝处理
if (nums[k] > target && (nums[k] >= 0 || target >= 0)) {
break; // 这里使用break统一通过最后的return返回
}
// 去重
if (k > 0 && nums[k] == nums[k - 1]) {
continue;
}
for (int i = k + 1; i < nums.size(); i++) {
// 2级剪枝处理
if (nums[k] + nums[i] > target && (nums[k] + nums[i] >= 0 || target >= 0)) {
break;
}
// 正确去重方法
if (i > k + 1 && nums[i] == nums[i - 1]) {
continue;

View File

@ -266,6 +266,26 @@ var merge = function(intervals) {
};
```
### TypeScript
```typescript
function merge(intervals: number[][]): number[][] {
const resArr: number[][] = [];
intervals.sort((a, b) => a[0] - b[0]);
resArr[0] = [...intervals[0]]; // 避免修改原intervals
for (let i = 1, length = intervals.length; i < length; i++) {
let interval: number[] = intervals[i];
let last: number[] = resArr[resArr.length - 1];
if (interval[0] <= last[1]) {
last[1] = Math.max(interval[1], last[1]);
} else {
resArr.push([...intervals[i]]);
}
}
return resArr;
};
```
-----------------------

View File

@ -30,7 +30,7 @@
相信很多同学刚开始做这种题目的时候,上来就是一波判断猛如虎。
结果运行的时候各种问题,然后开始各种修修补补,最后发现改了这里里有问题,改了那里这里又跑不起来了。
结果运行的时候各种问题,然后开始各种修修补补,最后发现改了这里里有问题,改了那里这里又跑不起来了。
大家还记得我们在这篇文章[数组:每次遇到二分法,都是一看就会,一写就废](https://programmercarl.com/0704.二分查找.html)中讲解了二分法,提到如果要写出正确的二分法一定要坚持**循环不变量原则**。
@ -47,7 +47,7 @@
可以发现这里的边界条件非常多,在一个循环中,如此多的边界条件,如果不按照固定规则来遍历,那就是**一进循环深似海从此offer是路人**。
这里一圈下来,我们要画每四条边,这四条边怎么画,每画一条边都要坚持一致的左闭右开,或者左开闭的原则,这样这一圈才能按照统一的规则画下来。
这里一圈下来,我们要画每四条边,这四条边怎么画,每画一条边都要坚持一致的左闭右开,或者左开闭的原则,这样这一圈才能按照统一的规则画下来。
那么我按照左闭右开的原则,来画一圈,大家看一下:
@ -59,7 +59,7 @@
一些同学做这道题目之所以一直写不好,代码越写越乱。
就是因为在画每一条边的时候,一会左开闭,一会左闭右闭,一会又来左闭右开,岂能不乱。
就是因为在画每一条边的时候,一会左开闭,一会左闭右闭,一会又来左闭右开,岂能不乱。
代码如下已经详细注释了每一步的目的可以看出while循环里判断的情况是很多的代码里处理的原则也是统一的左闭右开。

View File

@ -454,31 +454,36 @@ var partition = function(s) {
```typescript
function partition(s: string): string[][] {
function isPalindromeStr(s: string, left: number, right: number): boolean {
while (left < right) {
if (s[left++] !== s[right--]) {
return false;
const res: string[][] = []
const path: string[] = []
const isHuiwen = (
str: string,
startIndex: number,
endIndex: number
): boolean => {
for (; startIndex < endIndex; startIndex++, endIndex--) {
if (str[startIndex] !== str[endIndex]) {
return false
}
}
return true;
return true
}
function backTracking(s: string, startIndex: number, route: string[]): void {
let length: number = s.length;
if (length === startIndex) {
resArr.push(route.slice());
return;
const rec = (str: string, index: number): void => {
if (index >= str.length) {
res.push([...path])
return
}
for (let i = startIndex; i < length; i++) {
if (isPalindromeStr(s, startIndex, i)) {
route.push(s.slice(startIndex, i + 1));
backTracking(s, i + 1, route);
route.pop();
for (let i = index; i < str.length; i++) {
if (!isHuiwen(str, index, i)) {
continue
}
path.push(str.substring(index, i + 1))
rec(str, i + 1)
path.pop()
}
}
const resArr: string[][] = [];
backTracking(s, 0, []);
return resArr;
rec(s, 0)
return res
};
```

View File

@ -408,6 +408,9 @@ function canCompleteCircuit(gas: number[], cost: number[]): number {
### C
贪心算法:方法一
```c
int canCompleteCircuit(int* gas, int gasSize, int* cost, int costSize){
int curSum = 0;
@ -437,5 +440,36 @@ int canCompleteCircuit(int* gas, int gasSize, int* cost, int costSize){
}
```
贪心算法:方法二
```c
int canCompleteCircuit(int* gas, int gasSize, int* cost, int costSize){
int curSum = 0;
int totalSum = 0;
int start = 0;
int i;
for(i = 0; i < gasSize; ++i) {
// 当前i站中加油量与耗油量的差
int diff = gas[i] - cost[i];
curSum += diff;
totalSum += diff;
// 若0到i的加油量都为负则开始位置应为i+1
if(curSum < 0) {
curSum = 0;
// 当i + 1 == gasSize时totalSum < 0此时i为gasSize - 1),油车不可能返回原点
start = i + 1;
}
}
// 若总和小于0加油车无论如何都无法返回原点。返回-1
if(totalSum < 0)
return -1;
return start;
}
```
-----------------------
<div align="center"><img src=https://code-thinking.cdn.bcebos.com/pics/01二维码一.jpg width=500> </img></div>

View File

@ -109,7 +109,7 @@ public:
};
```
# 题外话
## 题外话
我们习惯看到的表达式都是中缀表达式,因为符合我们的习惯,但是中缀表达式对于计算机来说就不是很友好了。
@ -128,7 +128,7 @@ public:
# 其他语言版本
## 其他语言版本
java:

View File

@ -145,6 +145,38 @@ public:
## 其他语言版本
C:
用原来的链表操作:
```c
struct ListNode* removeElements(struct ListNode* head, int val){
struct ListNode* temp;
// 当头结点存在并且头结点的值等于val时
while(head && head->val == val) {
temp = head;
// 将新的头结点设置为head->next并删除原来的头结点
head = head->next;
free(temp);
}
struct ListNode *cur = head;
// 当cur存在并且cur->next存在时
// 此解法需要判断cur存在因为cur指向head。若head本身为NULL或者原链表中元素都为val的话cur也会为NULL
while(cur && (temp = cur->next)) {
// 若cur->next的值等于val
if(temp->val == val) {
// 将cur->next设置为cur->next->next并删除cur->next
cur->next = temp->next;
free(temp);
}
// 若cur->next不等于val则将cur后移一位
else
cur = cur->next;
}
// 返回头结点
return head;
}
```
设置一个虚拟头结点:
```c
/**
* Definition for singly-linked list.

View File

@ -112,7 +112,7 @@ public:
**一些录友会疑惑为什么时间复杂度是O(n)**
不要以为for里放一个while就以为是O(n^2)啊, 主要是看每一个元素被操作的次数,每个元素在滑动窗后进来操作一次,出去操作一次,每个元素都是被操作两次,所以时间复杂度是 2 × n 也就是O(n)。
不要以为for里放一个while就以为是O(n^2)啊, 主要是看每一个元素被操作的次数,每个元素在滑动窗后进来操作一次,出去操作一次,每个元素都是被操作两次,所以时间复杂度是 2 × n 也就是O(n)。
## 相关题目推荐

View File

@ -214,8 +214,8 @@ class Solution {
for (int i = 2; i <= prices.length; i++) {
/*
dp[i][0] 第i天持有股票收益;
dp[i][1] 第i天持有股票收益;
dp[i][0] 第i天持有股票收益;
dp[i][1] 第i天持有股票收益;
情况一第i天是冷静期不能以dp[i-1][1]购买股票,所以以dp[i - 2][1]买股票,没问题
情况二第i天不是冷静期理论上应该以dp[i-1][1]购买股票但是第i天不是冷静期说明第i-1天没有卖出股票
则dp[i-1][1]=dp[i-2][1],所以可以用dp[i-2][1]买股票,没问题

View File

@ -466,6 +466,55 @@ func sumOfLeftLeaves(_ root: TreeNode?) -> Int {
}
```
## C
递归法:
```c
int sumOfLeftLeaves(struct TreeNode* root){
// 递归结束条件若当前结点为空返回0
if(!root)
return 0;
// 递归取左子树的左结点和和右子树的左结点和
int leftValue = sumOfLeftLeaves(root->left);
int rightValue = sumOfLeftLeaves(root->right);
// 若当前结点的左结点存在,且其为叶子结点。取它的值
int midValue = 0;
if(root->left && (!root->left->left && !root->left->right))
midValue = root->left->val;
return leftValue + rightValue + midValue;
}
```
迭代法:
```c
int sumOfLeftLeaves(struct TreeNode* root){
struct TreeNode* stack[1000];
int stackTop = 0;
// 若传入root结点不为空将其入栈
if(root)
stack[stackTop++] = root;
int sum = 0;
//若栈不为空,进行循环
while(stackTop) {
// 出栈栈顶元素
struct TreeNode *topNode = stack[--stackTop];
// 若栈顶元素的左孩子为左叶子结点将其值加入sum中
if(topNode->left && (!topNode->left->left && !topNode->left->right))
sum += topNode->left->val;
// 若当前栈顶结点有左右孩子。将他们加入栈中进行遍历
if(topNode->right)
stack[stackTop++] = topNode->right;
if(topNode->left)
stack[stackTop++] = topNode->left;
}
return sum;
}
```
-----------------------

View File

@ -290,6 +290,54 @@ var reconstructQueue = function(people) {
};
```
### C
```c
int cmp(const void *p1, const void *p2) {
int *pp1 = *(int**)p1;
int *pp2 = *(int**)p2;
// 若身高相同则按照k从小到大排列
// 若身高不同,按身高从大到小排列
return pp1[0] == pp2[0] ? pp1[1] - pp2[1] : pp2[0] - pp1[0];
}
// 将start与end中间的元素都后移一位
// start为将要新插入元素的位置
void moveBack(int **people, int peopleSize, int start, int end) {
int i;
for(i = end; i > start; i--) {
people[i] = people[i-1];
}
}
int** reconstructQueue(int** people, int peopleSize, int* peopleColSize, int* returnSize, int** returnColumnSizes){
int i;
// 将people按身高从大到小排列若身高相同按k从小到大排列
qsort(people, peopleSize, sizeof(int*), cmp);
for(i = 0; i < peopleSize; ++i) {
// people[i]要插入的位置
int position = people[i][1];
int *temp = people[i];
// 将position到i中间的元素后移一位
// 注因为已经排好序position不会比i大。(举例排序后people最后一位元素最小其可能的k最大值为peopleSize-2小于此时的i)
moveBack(people, peopleSize, position, i);
// 将temp放置到position处
people[position] = temp;
}
// 设置返回二维数组的大小以及里面每个一维数组的长度
*returnSize = peopleSize;
*returnColumnSizes = (int*)malloc(sizeof(int) * peopleSize);
for(i = 0; i < peopleSize; ++i) {
(*returnColumnSizes)[i] = 2;
}
return people;
}
```
### TypeScript
```typescript
@ -309,5 +357,6 @@ function reconstructQueue(people: number[][]): number[][] {
-----------------------
<div align="center"><img src=https://code-thinking.cdn.bcebos.com/pics/01二维码一.jpg width=500> </img></div>

View File

@ -184,13 +184,14 @@ public:
class Solution {
public int eraseOverlapIntervals(int[][] intervals) {
Arrays.sort(intervals, (a, b) -> {
if (a[0] == a[0]) return a[1] - b[1];
return a[0] - b[0];
// 按照区间右边界升序排序
return a[1] - b[1];
});
int count = 0;
int edge = Integer.MIN_VALUE;
for (int i = 0; i < intervals.length; i++) {
// 若上一个区间的右边界小于当前区间的左边界,说明无交集
if (edge <= intervals[i][0]) {
edge = intervals[i][1];
} else {
@ -263,7 +264,7 @@ func min(a,b int)int{
}
return a
}
```
```
### Javascript:
- 按右边界排序
@ -306,6 +307,55 @@ var eraseOverlapIntervals = function(intervals) {
}
```
### TypeScript
> 按右边界排序,从左往右遍历
```typescript
function eraseOverlapIntervals(intervals: number[][]): number {
const length = intervals.length;
if (length === 0) return 0;
intervals.sort((a, b) => a[1] - b[1]);
let right: number = intervals[0][1];
let count: number = 1;
for (let i = 1; i < length; i++) {
if (intervals[i][0] >= right) {
count++;
right = intervals[i][1];
}
}
return length - count;
};
```
> 按左边界排序,从左往右遍历
```typescript
function eraseOverlapIntervals(intervals: number[][]): number {
if (intervals.length === 0) return 0;
intervals.sort((a, b) => a[0] - b[0]);
let right: number = intervals[0][1];
let tempInterval: number[];
let resCount: number = 0;
for (let i = 1, length = intervals.length; i < length; i++) {
tempInterval = intervals[i];
if (tempInterval[0] >= right) {
// 未重叠
right = tempInterval[1];
} else {
// 有重叠移除当前interval和前一个interval中右边界更大的那个
right = Math.min(right, tempInterval[1]);
resCount++;
}
}
return resCount;
};
```
-----------------------
<div align="center"><img src=https://code-thinking.cdn.bcebos.com/pics/01二维码一.jpg width=500> </img></div>

View File

@ -293,6 +293,50 @@ var maxProfit = function(prices, fee) {
};
```
TypeScript
> 贪心
```typescript
function maxProfit(prices: number[], fee: number): number {
if (prices.length === 0) return 0;
let minPrice: number = prices[0];
let profit: number = 0;
for (let i = 1, length = prices.length; i < length; i++) {
if (minPrice > prices[i]) {
minPrice = prices[i];
}
if (minPrice + fee < prices[i]) {
profit += prices[i] - minPrice - fee;
minPrice = prices[i] - fee;
}
}
return profit;
};
```
> 动态规划
```typescript
function maxProfit(prices: number[], fee: number): number {
/**
dp[i][1]: 第i天不持有股票的最大所剩现金
dp[i][0]: 第i天持有股票的最大所剩现金
*/
const length: number = prices.length;
const dp: number[][] = new Array(length).fill(0).map(_ => []);
dp[0][1] = 0;
dp[0][0] = -prices[0];
for (let i = 1, length = prices.length; i < length; i++) {
dp[i][1] = Math.max(dp[i - 1][1], dp[i - 1][0] + prices[i] - fee);
dp[i][0] = Math.max(dp[i - 1][0], dp[i - 1][1] - prices[i]);
}
return Math.max(dp[length - 1][0], dp[length - 1][1]);
};
```
-----------------------
<div align="center"><img src=https://code-thinking.cdn.bcebos.com/pics/01二维码一.jpg width=500> </img></div>

View File

@ -225,6 +225,28 @@ var monotoneIncreasingDigits = function(n) {
};
```
### TypeScript
```typescript
function monotoneIncreasingDigits(n: number): number {
let strArr: number[] = String(n).split('').map(i => parseInt(i));
const length = strArr.length;
let flag: number = length;
for (let i = length - 2; i >= 0; i--) {
if (strArr[i] > strArr[i + 1]) {
strArr[i] -= 1;
flag = i + 1;
}
}
for (let i = flag; i < length; i++) {
strArr[i] = 9;
}
return parseInt(strArr.join(''));
};
```
-----------------------
<div align="center"><img src=https://code-thinking.cdn.bcebos.com/pics/01二维码一.jpg width=500> </img></div>

View File

@ -229,6 +229,31 @@ var partitionLabels = function(s) {
};
```
### TypeScript
```typescript
function partitionLabels(s: string): number[] {
const length: number = s.length;
const resArr: number[] = [];
const helperMap: Map<string, number> = new Map();
for (let i = 0; i < length; i++) {
helperMap.set(s[i], i);
}
let left: number = 0;
let right: number = 0;
for (let i = 0; i < length; i++) {
right = Math.max(helperMap.get(s[i])!, right);
if (i === right) {
resArr.push(i - left + 1);
left = i + 1;
}
}
return resArr;
};
```
-----------------------
<div align="center"><img src=https://code-thinking.cdn.bcebos.com/pics/01二维码一.jpg width=500> </img></div>

View File

@ -250,6 +250,49 @@ var lemonadeChange = function(bills) {
return true
};
```
### C
```c
bool lemonadeChange(int* bills, int billsSize){
// 分别记录五元、十元的数量二十元不用记录因为不会用到20元找零
int fiveCount = 0; int tenCount = 0;
int i;
for(i = 0; i < billsSize; ++i) {
// 分情况讨论每位顾客的付款
switch(bills[i]) {
// 情况一:直接收款五元
case 5:
fiveCount++;
break;
// 情况二:收款十元
case 10:
// 若没有五元找零返回false
if(fiveCount == 0)
return false;
// 收款十元并找零五元
fiveCount--;
tenCount++;
break;
// 情况三:收款二十元
case 20:
// 若可以优先用十元和五元找零因为十元只能找零20所以需要尽量用掉。而5元能找零十元和二十元
if(fiveCount > 0 && tenCount > 0) {
fiveCount--;
tenCount--;
}
// 若没有十元,但是有三张五元。用三张五元找零
else if(fiveCount >= 3)
fiveCount-=3;
// 无法找开返回false
else
return false;
break;
}
}
// 全部可以找开返回true
return true;
}
```
### TypeScript

View File

@ -266,7 +266,61 @@ public class Solution {
## Python
```Python
```Python3
class TreeNode:
def __init__(self, val = 0, left = None, right = None):
self.val = val
self.left = left
self.right = right
# 根据数组构建二叉树
def construct_binary_tree(nums: []) -> TreeNode:
if not nums:
return None
# 用于存放构建好的节点
root = TreeNode(-1)
Tree = []
# 将数组元素全部转化为树节点
for i in range(len(nums)):
if nums[i]!= -1:
node = TreeNode(nums[i])
else:
node = None
Tree.append(node)
if i == 0:
root = node
for i in range(len(Tree)):
node = Tree[i]
if node and (2 * i + 2) < len(Tree):
node.left = Tree[i * 2 + 1]
node.right = Tree[i * 2 + 2]
return root
# 算法:中序遍历二叉树
class Solution:
def __init__(self):
self.T = []
def inorder(self, root: TreeNode) -> []:
if not root:
return
self.inorder(root.left)
self.T.append(root.val)
self.inorder(root.right)
return self.T
# 验证创建二叉树的有效性,二叉排序树的中序遍历应为顺序排列
test_tree = [3, 1, 5, -1, 2, 4 ,6]
root = construct_binary_tree(test_tree)
A = Solution()
print(A.inorder(root))
```

View File

@ -43,19 +43,19 @@
**那么二维数组在内存的空间地址是连续的么?**
我们来举一个例子,例如: `int[][] rating = new int[3][4];` 这个二维数在内存空间可不是一个 `3*4` 的连续地址空间
我们来举一个Java的例子,例如: `int[][] rating = new int[3][4];` 这个二维数在内存空间可不是一个 `3*4` 的连续地址空间
看了下图,就应该明白了:
<img src='https://code-thinking.cdn.bcebos.com/pics/%E7%AE%97%E6%B3%95%E9%80%9A%E5%85%B3%E6%95%B0%E7%BB%843.png' width=600> </img></div>
所以**二维数在内存中不是 `3*4` 的连续地址空间,而是四条连续的地址空间组成!**
所以**Java的二维数在内存中不是 `3*4` 的连续地址空间,而是四条连续的地址空间组成!**
# 数组的经典题目
在面试中,数组是必考的基础数据结构。
其实数的题目在思想上一般比较简单的,但是如果想高效,并不容易。
其实数的题目在思想上一般比较简单的,但是如果想高效,并不容易。
我们之前一共讲解了四道经典数组题目,每一道题目都代表一个类型,一种思想。
@ -115,7 +115,7 @@
在这道题目中,我们再一次介绍到了**循环不变量原则**,其实这也是写程序中的重要原则。
相信大家遇到过这种情况: 感觉题目的边界调节超多,一波接着一波的判断,找边界,了东墙补西墙,好不容易运行通过了,代码写的十分冗余,毫无章法,其实**真正解决题目的代码都是简洁的,或者有原则性的**,大家可以在这道题目中体会到这一点。
相信大家遇到过这种情况: 感觉题目的边界调节超多,一波接着一波的判断,找边界,了东墙补西墙,好不容易运行通过了,代码写的十分冗余,毫无章法,其实**真正解决题目的代码都是简洁的,或者有原则性的**,大家可以在这道题目中体会到这一点。
# 总结

View File

@ -24,7 +24,7 @@
## 双链表
单链表中的节点只能指向节点的下一个节点。
单链表中的指针域只能指向节点的下一个节点。
双链表:每一个节点有两个指针域,一个指向下一个节点,一个指向上一个节点。
@ -56,7 +56,7 @@
![链表3](https://img-blog.csdnimg.cn/20200806194613920.png)
这个链表起始节点为2 终止节点为7 各个节点分布在内存不同地址空间上,通过指针串联在一起。
这个链表起始节点为2 终止节点为7 各个节点分布在内存不同地址空间上,通过指针串联在一起。
# 链表的定义