1. 怎样把一个单链表反序
- 反转一个链表,循环算法
List reverse(List n){
if(!n){
return n;
}
list cur = n.next;
list pre = n;
list tmp;
pre.next = null;
while(NULL != cur.next){
tmp = cur;
tmp.next = pre;
pre = tmp;
cur = cur.next;
}
return tmp;
}
//1-2-3-4-5-6
- 反转一个链表,递归算法
List *reverese(List *oldList , List *newHead = NULL){
List *next = oldList->next; // 记录上次翻转后的链表
oldList->next = newHead; // 将当前结点插入到反转后链表开头
newHead = oldList; // 递归处理剩余的链表
return (next == NULL) ? newHead : reverse(t , newHead);
}
解释:
这段代码是用来实现单链表反转的。首先,让我解释一下单链表。链表是由一系列节点组成的,每个节点包含一个值(数据)和一个指向下一个节点的指针。
这段反转链表的代码的原理是通过反转链表中的节点的指向关系来完成的。在这个过程中,主要会用到三个指针:**current(当前节点),previous(前一节点)和temp(临时节点)**来完成反转。
下面,我们一步步解析这段代码:
-
如果链表为空或者只有一个节点,那么直接返回这个链表。
-
初始时设置previous指针指向第一个节点,current指针指向第二个节点,因为首节点反转后将变成尾节点,所以要将其指向设置为null。
-
开始循环,这里的目的是反转从当前节点开始的所有节点。
-
我们首先让temp指向current,这是为了保存当前节点的位置。
-
然后将current节点的next指向previous,完成一次节点的反转。
-
接着,我们把previous和current节点向前移动一位。具体操作是让previous指向temp(也就是之前的current),再让current指向next节点(也就是current.next)。
-
-
循环结束后,我们让temp(即最后的previous)作为新的头节点返回,因为在反转后的链表中,原链表的最后一个节点现在是第一个节点。
2. 能否用两个栈实现一个队列的功能
typedef struct node
{
int data;
node *next;
}node,*LinkStack;
创建空栈:
LinkStack CreateNULLStack( LinkStack &S)
{
S = (LinkStack)malloc( sizeof( node ) ); //申请新结点
if( NULL == S)
{
printf("Fail to malloc a new node.\n");
return NULL;
}
S->data = 0; //初始化新结点
S->next = NULL;
return S;
}
栈的插入函数:
LinkStack Push( LinkStack &S, int data)
{
if( NULL == S) //检验栈
{
printf("There no node in stack!");
return NULL;
}
LinkStack p = NULL;
p = (LinkStack)malloc( sizeof( node ) ); //申请新结点
if( NULL == p)
{
printf("Fail to malloc a new node.\n");
return S;
}
if( NULL == S->next)
{
p->next = NULL;
}
else
{
p->next = S->next;
}
p->data = data; //初始化新结点
S->next = p; //插入新结点
return S;
}
出栈函数:
node Pop( LinkStack &S)
{
node temp;
temp.data = 0;
temp.next = NULL;
if( NULL == S) //检验栈
{
printf("There no node in stack!");
return temp;
}
temp = *S;
if( S->next == NULL )
{
printf("The stack is NULL,can't pop!\n");
return temp;
}
LinkStack p = S ->next; //节点出栈
S->next = S->next->next;
temp = *p;
free( p );
p = NULL;
return temp;
}
双栈实现队列的入队函数:
LinkStack StackToQueuPush( LinkStack &S, int data)
{
node n;
LinkStack S1 = NULL;
CreateNULLStack( S1 ); //创建空栈
while( NULL != S->next ) //S出栈入S1
{
n = Pop( S );
Push( S1, n.data );
}
Push( S1, data ); //新结点入栈
while( NULL != S1->next ) //S1出栈入S
{
n = Pop( S1 );
Push( S, n.data );
}
return S;
}
请注意:
用两个栈可以实现一个队列的功能,用两个队列实现不了一个栈的功能。
3. 计算一棵二叉树的深度
int depth(BiTree T){
if(!T)
return 0;
int d1 = depth(T->lchild);
int d2 = depth(T->rchild);
return (d1 > d2 ? d1 : d2) + 1;
}
4. 编码实现直接插入排序
直接插入排序(Straight Insertion Sort)的基本思想是:把n个待排序的元素看成为一个有序表和一个无序表。开始时有序表中只包含1个元素,无序表中包含有n-1个元素,排序过程中每次从无序表中取出第一个元素,将它插入到有序表中的适当位置,使之成为新的有序表,重复n-1次可完成排序过程。
O(N2)
#include<bits/stdc++.h>
using namespace std;
int main(){
int ARRAY[10] = { 0, 6, 3, 2, 7, 5, 4, 9, 1, 8 };
int i , j;
for(int i = 0 ; i < 10 ; i++){
cout << ARRAY[i] << " ";
}
cout << endl;
for(int i = 1 ; i < 10 ; i++){
if(ARRAY[i] < ARRAY[i-1]){
ARRAY[0] = ARRAY[i];
j = i - 1;
do{
ARRAY[j+1] = ARRAY[j];
j--;
}while(ARRAY[0] < ARRAY[j]);
ARRAY[j+1] = ARRAY[0];
}
}
for(i = 0 ; i < 10 ; i++)
cout << ARRAY[i] << " ";
cout << endl;
return 0;
}
请注意:
所有为简化边界条件而引入的附加结点(元素)均可称为哨兵。引入哨兵后使得查找循环条件的时间大约减少了一半,对于记录数较大的文件节约的时间就相当可观。类似于排序这样使用频率非常高的算法,要尽可能地减少其运行时间。
5. 编码实现冒泡排序
O(N2)
#include<bits/stdc++.h>
using namespace std;
int main(){
int ARRAY[10] = { 0, 6, 3, 2, 7, 5, 4, 9, 1, 8 }; //待排序数组
cout << endl;
for(int a = 0 ; a < LEN ; a++)
cout << ARRAY[a] << " ";
cout << endl;
int i = 0 , j = 0;
bool isChange;
for(i = 1 ; i < LEN ; i++){
isChange = 0;
for(j = LEN - 2 ; j >= i ; j--){
if(ARRAY[j+1] < ARRAY[j]){
ARRAY[0] = ARRAY[j+1];
ARRAY[j+1] = ARRAY[j];
ARRAY[j] = ARRAY[0];
isChange = 1;
}
}
cout << endl;
for(int a = 0 ; a < LEN ; a++)
cout << ARRAY[a] << " ";
if(!isChange)
break;
}
cout << endl;
return 0;
}
6. 编码实现直接选择排序
不稳定
1、堆排序、快速排序、希尔排序、直接选择排序不是稳定的排序算法;
2、基数排序、冒泡排序、直接插入排序、折半插入排序、归并排序是稳定的排序算法。
O(N2)
#include<bits/stdc++.h>
using namespace std;
#define LEN 9
int main(){
int ARRAY[LEN]={ 5, 6, 8, 2, 4, 1, 9, 3, 7 }; //待序数组
cout << "Before sorted:" << endl;
for(int m = 0 ; m < LEN ; m++)
cout << ARRAY[m] << " ";
cout << endl;
for(int i = 1 ; i <= LEN-1 ; i++){
int t = i - 1;
int temp = 0;
for(int j = i ; j < LEN ; j++){
if(ARRAY[j] < ARRAY[t])
t = j;
}
if(t != i-1){
temp = ARRAY[i-1];
ARRAY[i-1] = ARRAY[t];
ARRAY[t] = temp;
}
}
cout << endl;
cout << "After sorted:" << endl;
for(int i = 0 ; i < LEN ; i++)
cout << ARRAY[i] << " ";
cout << endl;
}
7. 编程实现堆排序
堆又可称之为完全二叉堆。这是一个逻辑上基于完全二叉树、物理上一般基于线性数据结构(如数组、向量、链表等)的一种数据结构。
#include<bits/stdc++.h>
using namespace std;
#define LEN 9
void createHeep(int ARRAY[] , int sPoint , int Len)//生成大根堆
{
while((2*sPoint + 1) < Len){
int mPoint = 2*sPoint + 1;
if((2*sPoint + 2) < Len){
if(ARRAY[2*sPoint+1] < ARRAY[2*sPoint+2])
mPoint = 2 * sPoint + 2;
}
if(ARRAY[sPoint] < ARRAY[mPoint])//堆被破坏,需要重新调整
{
int tmpData = ARRAY[sPoint];
ARRAY[sPoint] = ARRAY[mPoint];
ARRAY[mPoint] = tmpData;
sPoint = mPoint;
}
else
break;
}
return ;
}
void heepSort(int ARRAY[] , int Len)//堆排序
{
int i = 0;
for(i = (Len/2-1) ; i >= 0 ; i--)//将Hr[0,Length-1]建成大根堆
{
createHeep(ARRAY , i , Len);
}
for ( i = Len - 1; i > 0; i-- )
{
int tmpData = ARRAY[0]; //与最后一个记录交换
ARRAY[0] = ARRAY[i];
ARRAY[i] = tmpData;
createHeep( ARRAY, 0, i ); //将H.r[0..i]重新调整为大根堆
}
return;
}
int main(){
int ARRAY[] ={ 5, 4, 7, 3, 9, 1, 6, 8, 2};
printf("Before sorted:\n"); //打印排序前数组内容
for ( int i = 0; i < 9; i++ )
{
printf("%d ", ARRAY[i]);
}
printf("\n");
heepSort( ARRAY, 9 ); //堆排序
printf("After sorted:\n"); //打印排序后数组内容
for(int i = 0; i < 9; i++ )
{
printf( "%d ", ARRAY[i] );
}
printf( "\n" );
return 0;
}
解释:
这段代码会将一个乱序的数组通过堆排序变为升序排列。我们首先理解一下堆排序的基本原理,之后再细解这段代码。
堆排序的基本思想是:将待排序的序列构造成一个大顶堆,此时整个序列的最大值就是堆顶的根节点。然后将其与堆的最后一个元素交换,此时末尾为最大值。然后将剩余N-1个节点重新构造成大顶堆,如此反复进行。
首先,来看createHeep
函数:
createHeep函数主要用途是调整堆,使之变为大顶堆。函数的参数有初始化的堆数组ARRAY
、调整开始的起点sPoint
和堆的大小Len
。在处理过程中,首先检查该节点是否有左子节点(2 * sPoint + 1 < Len),然后看是否有右子节点并且右子节点的值是否大于左子节点的值,如果是,则mPoint指向右子节点。然后比较父节点和mPoint指向的子节点,如果父节点小于子节点,则交换两者的值,并且开始调整以mPoint为根节点的堆。这个过程一直重复直到树成为一个大顶堆。
然后看heepSort
函数:
heepSort函数的主要任务是接受一个数组并且藉由调用createHeep函数将它变为一个大顶堆。先通过一次反向循环,将数组从中间到首部全部变为大顶堆。然后通过另一次从末尾到首部的反向循环交换首尾节点,每次交换后,长度减一并重新调整堆。最后,整个数组就变成了有序数组。
最后是main
函数:
main函数首先声明了一个乱序数组,并打印初始数组。然后调用heapSort进行排序,并再次打印。排序函数heapSort利用了heapify进行大堆顶构造和排序,最终实现了堆排序。
整体来说,这个代码主要功能是通过建立大根堆并进行调整来实现堆排序。
8. 编程实现基数排序
#include<bits/stdc++.h>
using namespace std;
#define BASE 10
void radixsort(int arr[] , int size){
if(arr == NULL)
return;
//找出最大的数
int max = arr[0];
for(int i = 1 ; i < size ; i++){
if(arr[i] > max)
max = arr[i];
}
int exp = 1;//位数
int *temp = (int*) malloc(size * sizeof(int));
while(max / exp > 0){
//重置基数桶
int bucket[BASE] = {0};
//统计每个基数上有多少个数据
for(int i = 0 ; i < size ; i++)
bucket[(arr[i] / exp) % BASE]++;
//求出基数桶的边界索引,bucket[i]值为第i个桶的有边界索引+1
cout << "&&" << endl;
for(int i = 0 ; i < 10 ; i++)
cout << bucket[i] << " ";
cout << endl;
cout << "&&" << endl;
for(int i = 1 ; i < BASE ; i++)
bucket[i] += bucket[i-1];
cout << "$$" << endl;
for(int i = 0 ; i < 10 ; i++)
cout << bucket[i] << " ";
cout << endl;
cout << "$$" << endl;
//这里要从右到左扫描,保证排序稳定性
for(int i = size - 1 ; i >= 0 ; i--)
//复制到原数组,完成一趟排序
temp[--bucket[(arr[i]/exp)%BASE]] = arr[i];
for(int i = 0 ; i < size ; i++)
arr[i] = temp[i];
//位数递增
exp *= BASE;
for(int i = 0 ; i < size ; i++)
printf("%d ",arr[i]);
cout << endl;
}
free(temp);
}
int main(){
int arr[] = {27, 91, 1, 97, 17, 23, 84, 28, 72, 5, 67, 25};
int size = sizeof(arr) / sizeof(int);
for(int i = 0 ; i < size ; i++)
printf("%d ",arr[i]);
cout << endl;
radixsort(arr , size);
for(int i = 0 ; i < size ; i++)
printf("%d ",arr[i]);
cout << endl;
return 0;
}
9. 每个字母对应一个数字,a对应1,b对应2,…z对应26,给出一个数字串,问有多少种对应的原串?对答案取模1e9+7
使用动态规划来实现。因为数字对应字母有两种情况,一种是一位数对应字母,一种是两位数对应字母,因此状态转移方程有两种,一种是从前一个转移,一种是从前两个位置转移。
#include<bits/stdc++.h>
using namespace std;
const int MOD = 1e9 + 7;
int numDecodings(const string& s) {
if (s.empty() || s[0] == '0') {
return 0; // 无效输入,首字符为 '0' 时无解
}
int n = s.size();
vector<int> dp(n + 1, 0);
dp[0] = 1; // 空串
for (int i = 1; i <= n; ++i) {
// 单个字符
if (s[i - 1] != '0') {
dp[i] = (dp[i] + dp[i - 1]) % MOD;
}
// 两个字符
if (i > 1 && s[i - 2] != '0') {
int two_digit = stoi(s.substr(i - 2, 2)); // 取出两位数字
if (10 <= two_digit && two_digit <= 26) {
dp[i] = (dp[i] + dp[i - 2]) % MOD;
}
}
}
return dp[n];
}
int main() {
string s;
cin >> s;
int res = numDecodings(s);
cout << res << endl;
return 0;
}
10. 区间划分
定义一个序列的权重为序列中至少出现过一次的数字之和,现在给你长度为m的序列,要将序列化分为k段连续的序列,请问划分后的最大权重之和是多少?
7 3
1 2 3 1 1 1 2 3
1+2+3+1+1+2+3
这道题咋一看可能没什么思路,思考一下你会发现,我们要尽可能地让相同的数值分在不同的区间中,这样才会使得结果最大。所以这道题还是要用DP来解决。
定义 dp[i][j]
为将前 i
个元素划分为 j
段的最大权重和。
dp[i][j]=max(dp[p][j−1]+weight(s[p+1]…s[i]))
#include <iostream>
#include <vector>
#include <unordered_set>
#include <algorithm>
using namespace std;
int maxWeightSum(const vector<int>& seq, int m, int k) {
vector<vector<int>> dp(m + 1, vector<int>(k + 1, INT_MIN));
dp[0][0] = 0; // Base case
for (int j = 1; j <= k; ++j) {//从划分一个区间到划分K个区间
for (int i = 1; i <= m; ++i) {//从头开始遍历
int weight = 0;
unordered_set<int> uniqueNumbers;
for (int p = i; p >= 1; --p) {//从当前位置向前遍历,如果这个数没出现过,就放到集合里,然后weight增加对应的值
if (uniqueNumbers.find(seq[p - 1]) == uniqueNumbers.end()) {
uniqueNumbers.insert(seq[p - 1]);
weight += seq[p - 1];
}
// Update dp[i][j] considering the segment seq[p-1] to seq[i-1]
dp[i][j] = max(dp[i][j], dp[p - 1][j - 1] + weight);//状态转移方程,将前 i个元素划分为 j 段的最大权重和=将前 p-1个元素划分为 j-1 段的最大权重和+p到i的权重
}
}
}
return dp[m][k];
}
int main() {
int m, k;
cout << "Enter the length of the sequence (m): ";
cin >> m;
vector<int> seq(m);
cout << "Enter the sequence elements: ";
for (int i = 0; i < m; ++i) {
cin >> seq[i];
}
cout << "Enter the number of segments (k): ";
cin >> k;
int result = maxWeightSum(seq, m, k);
cout << "The maximum weight sum after partitioning is: " << result << endl;
return 0;
}