定义如下:
线性表是指零个或者多个数据元素的有限序列;
线性表是一个序列,也就是元素之间是有顺序的,若元素存在多个,则第一个元素无前驱,最后一个元素无后继,其它每个元素都有且只有一个前驱和后继。
线性表有两种存储结构,一种是顺序存储[数组],一种是链式存储[链表]。
栈与队列都是操作受限的线性表,这两个将会在下一篇文章介绍,这篇文章先介绍数组和链表。
数组是一种线性表数据结构。用一组连续的内存空间,存储一组具有相同类型的数据。
正是因为数组用连续的内存空间存储相同类型的数据,才能够实现随机访问,相应的弊端就是插入,删除时为了保证数据的连续性需要做大量的数据迁移工作。
假设数组的长度为n,现在需要将一个数据插入到数组的第k个位置。为了把第k个位置腾出来给新数据使用,需要将k~n这部分的元素都顺序地向后挪一位,插入操作的时间复杂度为多少?
如果在数组的末尾插入元素,不需要移动数据,时间复杂度为O(1)。
在数组的开头插入元素,所有的数据都需要依次向后移动一位,最坏的时间复杂度为O(n)。由于在每个位置插入元素的概率是一样的,平均时间复杂度为(1+2+3…+n)/n = O(n)
如果数组中的数据是有序的,在某个位置插入一个新的元素,必须迁移插入位置之后的元素。但是如果数组中存储的数据没有任何规律只是被当作一个存储数据的集合,在这种情况下,将元素插入到第k个位置,为了避免大规模的数据迁移,可以直接将第k个位置的数据迁移到末尾,把新的元素直接放入第k个位置。[基于数据交换的思想]
若删除数组中第k个位置的元素,时间复杂度为多少?
删除数组末尾的数据,最好情况时间复杂度为O(1)
删除数组开头的数据,最坏情况时间复杂度为O(n)
平均时间复杂度为(1+2+3…+n)/n=O(n)
实际上,在某些特殊的场景下,并不一定非要保持数组中数据的连续性。如果将多次删除操作集中在一起执行,删除的效率就会提高。[写到这里想到mysql-DML中的delete操作,delete操作并不是真正的将操作的记录删除,而是对这条记录打上一个"delete flag"的标识,标记这个记录已经被删除这条记录所占用的内存空间可以被复用,这样一来可以节省开销]
可以先记录已被删除的数据,每次删除操作并不迁移数据,当数组没有更多存储空间时,再执行真正的删除操作,大大减少删除操作导致的数据迁移。
针对数组类型很多语言提供了容器类,比如C++STL中的vector,Java中的ArrayList,go中的slice。
容器最大的优势就是将很多数组操作的细节封装起来,并且可以支持动态扩容。[扩容操作涉及的内存申请和数据迁移是比较耗时的]
什么时候适合用数组,什么时候适合用容器?
** 为什么数组下标从0开始**
从数组存储的内存模型上看,“下标”最确切的定义应该是偏移(offset)。如果用a表示数组的首地址,a[0]就是偏移为0的位置,也就是首地址,a[k]就表示偏移k个type_size的位置,计算a[k]的内存地址只需要用如下的公式
a[k]_address = base_address + k*type_size
但是,如果数组从1开始计数,计算公式如下:
a[k]_address = base_address + (k-1)*type_size
对比两个公式,可以发现从1开始编号,每次随机访问数组都多了一次减法运算,对CPU来说就是多了一次减法指令。数组作为非常基础的数据结构,通过下标随机访问数组元素是非常基础的编程操作,效率的优化就要尽可能做到极致。为了减少一次减法操作,数组选择从0开始编号,而不是从1开始。
最主要的原因可能是历史原因。
C语言设计者用0开始计数数组下标,之后的Java,JavaScript等高级语言效仿了C语言,因此继续沿用从0开始计数的习惯。实际上很多语言中数组也并不是从0开始计数,比如Matlab从1开始计数,Python还支持负数下标。
二维数组的寻址公式
a[i][j]_address = base_address+(i*n+j)*type_size
0<=i<=m,0<=j<=n
接下来看一下有关“数组”的算法题
题目
给定一个n个元素有序的(升序)整型数组nums和一个目标值target,写一个函数搜索nums中的target,如果目标值存在则返回下标,否则返回-1;
其中n在[1,10000]之间,nums的每个元素都将在[-9999,9999]之间
输入
首先输入一个正整数n和一个正整数target分别表示nums的元素个数和要找的目标值,另起一行输入各个元素。
输出
目标值存在输出下标,不存在输出-1
测试样例
样例1
6 9
-1 0 3 5 9 12
4
样例2
6 2
-1 0 3 5 9 12
-1
思路:
采用"二分查找"进行编程
#include<iostream>
#include<vector>
using namespace std;
int my_binary_search(const vector<int>&,int);
int main(){
int n,target;cin>>n>>target;
vector<int>nums(n,0);
for(int i=0;i<n;++i) cin>>nums[i];
cout<<my_binary_search(nums,target)<<endl;
return 0;
}
//这里使用的是容器所以不需要传递nums中的元素个数n
//如果这里使用的是数组,需要传递nums的长度n
int my_binary_search(const vector<int>&nums,int num){
int n = nums.size();
//int l=0,r=n-1;//左右边界[左,右]
//while(l<=r){
// int mid = l+((r-l)>>1);
// if(nums[mid]>num) r=mid-1;
// else if(nums[mid]<num) l=mid+1;
// else return mid;
//}
int l=0,r=n;//[左,右)
while(l<r){
int mid = l+((r-l)>>1);
if(nums[mid]>num) r=mid;
else if(nums[mid]<num) l=mid+1;
else return mid;
}
return -1;
}
package main
import "fmt"
func main(){
var n,target int
fmt.Scan(&n,&target);
nums:=make([]int,n)
for i:=0;i<n;i++{
fmt.Scan(&nums[i])
}
var my_bs func()int
l,r:=0,n-1
my_bs=func()int{
for l<=r{
//算术运算符的优先级高于位移运算符
mid:=l+((r-l)>>1)
if nums[mid]>target{
r=mid-1
}else if nums[mid]<target{
l=mid+1
}else {
return mid
}
}
return -1
}
fmt.Println(my_bs())
}
题目
给定一个数组nums和一个值val,原地移除所有数值等于val的元素,返回移除后数组的长度和数组的各个元素
元素的顺序可以改变,必须是原地修改,不需要考虑超出新长度后面的元素
0<=nums.length<=100
0<=nums[i]<=50
0<=val<=100
输入
输入n和val表示数组的长度和删除的目标值,紧接着另起一行输入n个数组的元素。
输出
输出移除后数组的长度,另起一行输出移除后数组的各个元素
测试样例
样例1
4 3
3 2 2 3
2
2 2
样例2
8 2
5
0 1 3 0 4
思路:
#include<iostream>
#include<vector>
using namespace std;
int main(){
int n,val;
cin>>n>>val;
vector<int>nums(n);
for(int i=0;i<n;++i) cin>>nums[i];
//remove num
int l=0;
for(int r=0;r<n;++r) if(nums[r]!=val) nums[l++]=nums[r];
//the len of new nums is l
cout<<l<<endl;
for(int i=0;i<l;++i) {
if(i!=0) cout<<" ";
cout<<nums[i];
}
cout<<endl;
return 0;
}
package main
import "fmt"
func main(){
var n,val int
fmt.Scan(&n,&val)
nums:=make([]int,n)
for i:=0;i<n;i++{
fmt.Scan(&nums[i])
}
//remove num
l:=0
for r:=0;r<n;r++{
if nums[r]!=val{
nums[l]=nums[r]
l++
}
}
fmt.Println(l)
for i:=0;i<l;i++{
if i!=0{
fmt.Print(" ")
}
fmt.Print(nums[i])
}
}
题目
给定一个按照非递减顺序排序的整数数组nums,返回每个数字的平方组成的新数组,要求新数组也按照非递减的顺序排序
1<=nums.length<= 1 0 4 10^4 104
- 1 0 4 10^4 104<=nums[i]<= 1 0 4 10^4 104
输入
输入n表示数组的长度,另起一行输入n个数组元素
输出
按照非递减顺序排序的平方数组
测试样例
样例1
5
-4 -1 0 3 10
0 1 9 16 100
样例2
5
-7 -3 2 3 11
4 9 9 49 121
思路:
有没有更简便一点的方法呢?在遍历平方的同时进行排序。
#include<iostream>
#include<vector>
using namespace std;
int main(){
int n;cin>>n;
vector<int>nums(n);
for(int i=0;i<n;++i) cin>>nums[i];
vector<int>ans(n);
int in=n-1,l=0,r=n-1;
while(l<=r){
int n1=nums[l]*nums[l],n2=nums[r]*nums[r];
if(n1>n2) {
ans[in--]=n1;
++l;
}else{
ans[in--]=n2;
--r;
}
}
for(const int&a:ans) {
cout<<a<<" ";
}
return 0;
}
package main
import "fmt"
func main(){
var n int
fmt.Scan(&n)
nums,ans:=make([]int,n),make([]int,n)
for i:=0;i<n;i++{
fmt.Scan(&nums[i])
}
in,l,r:=n-1,0,n-1
for l<=r{
n1:=nums[l]*nums[l]
n2:=nums[r]*nums[r]
if n1>n2{
ans[in]=n1
l++
}else{
ans[in]=n2
r--
}
in--
}
for _,num:=range ans{
fmt.Print(num," ")
}
}
题目
给定一个含有n个正整数的数组和一个正整数target。
找出该数组中满足其总和大于等于target的长度最小的连续子数组,返回其长度和连续数组。不存在符合条件的子数组返回0
输入
输入正整数n和target表示数组的长度和目标总和,另起一行输入n个数组元素
输出
满足条件的数组长度和对应的数组元素
测试样例
样例1
6 7
2 3 1 2 4 3
2
4 3
样例2
3 4
1 4 4
1
4
样例3
8 11
1 1 1 1 1 1 1 1
0
思路:
如何对上述的方法进行优化呢?上述的双层for循环做了很多的重复工作,一个元素可能出现在某个子数组的开头或者中间或者末尾导致这个元素被计算了很多遍。有没有一种方法,每个元素只遍历一遍只计算一遍。
#include<iostream>
#include<vector>
using namespace std;
int main(){
int n,target;
cin>>n>>target;
vector<int>nums(n);
for(int i=0;i<n;++i) cin>>nums[i];
int minlen=n+1,al=-1,ar=-1;
int sum=0,l=0;
for(int r=0;r<n;++r){
sum+=nums[r];
while(sum>=target){
int len=r-l+1;
if(len<minlen){
minlen=len;
al=l;
ar=r;
}
sum-=nums[l];
l++;
}
}
if(minlen==n+1) cout<<0<<endl;
else {
cout<<minlen<<endl;
for(int i=al;i<=ar;++i) cout<<nums[i]<<" ";
}
}
package main
import "fmt"
func main(){
var n,target int
fmt.Scan(&n,&target)
nums:=make([]int,n)
for i:=0;i<n;i++ {
fmt.Scan(&nums[i])
}
minlen,al,ar:=n+1,-1,-1
sum,l:=0,0
for r:=0;r<n;r++{
sum+=nums[r]
for sum>=target{
len:=r-l+1
if len<minlen{
minlen=len
al,ar=l,r
}
sum-=nums[l]
l++
}
}
if minlen==n+1{
fmt.Println(0)
}else{
fmt.Println(minlen)
for i:=al;i<=ar;i++ {
fmt.Print(nums[i]," ")
}
}
}
题目
给定一个正整数n,生成一个包含1~ n 2 n^2 n2所有元素,且元素按照顺时针顺序螺旋排列的n*n正方形矩阵
输入
输入正整数n
输出
输出n*n的矩阵包含1~ n 2 n^2 n2的所有元素,且元素按照顺时针螺旋排列
测试样例
样例1
1
1
样例2
3
1 2 3
8 9 4
7 6 5
思路:
#include<iostream>
#include<vector>
using namespace std;
int main(){
int n;cin>>n;
//int matrix[n][n]
vector<vector<int>> matrix(n,vector<int>(n,0));
int num=1;//from 1 to n^2
for(int c=0;c<n/2;++c){
//c表示每一圈的起始位置,同时也表示填充的圈数
int row=c,col=c;
//hang[left,right)
for(;col<n-c-1;++col) matrix[row][col]=num++;
//lie
for(;row<n-c-1;++row) matrix[row][col]=num++;
//hang
for(;col>c;--col) matrix[row][col]=num++;
//lie
for(;row>c;--row) matrix[row][col]=num++;
}
//n is odd?
if(n&1) matrix[n/2][n/2]=num;
for(int i=0;i<n;++i){
for(int j=0;j<n;++j){
if(j!=0) cout<<" ";
cout<<matrix[i][j];
}
cout<<endl;
}
}
package main
import "fmt"
func main(){
var n int
fmt.Scan(&n)
num:=1
matrix:=make([][]int,n)
for i:=0;i<n;i++{
matrix[i]=make([]int,n)
}
for c:=0;c<n/2;c++{
row,col:=c,c
for ;col<n-c-1;col++ {
matrix[row][col]=num
num++
}
for ;row<n-c-1;row++{
matrix[row][col]=num
num++
}
for ;col>c;col--{
matrix[row][col]=num
num++
}
for ;row>c;row--{
matrix[row][col]=num
num++
}
}
if n&1==1 {matrix[n/2][n/2]=num}
for i:=0;i<n;i++{
for j:=0;j<n;j++{
if j!=0 {
fmt.Print(" ")
}
fmt.Print(matrix[i][j])
}
fmt.Println()
}
}
数组存储元素需要连续的内存存储空间。而链表不需要,它通过"指针"将一组零散的内存块串联起来使用,这些内存块称为链表的"结点",为了将所有的结点串起来,每个链表的结点除了存储数据之外,还需要记录链上结点的地址。根据记录的结点不同,又分为不同的链表。
每个结点除了存储数据之外,只存储下一个结点的地址[next后继指针]
循环链表是一种特殊的单链表,与单链表唯一的区别就在于尾结点指向的不是空地址NULL,而是第一个结点头结点,形成一个环状的结构。
循环链表的优点是从链尾到链头比较方便。
每个结点除了存储数据之外,还存储上一个结点[prev前驱指针]和下一个结点的地址[后继指针]。
从上图可以看出,双向链表相比于单链表多了一个额外的存储空间存储前驱结点的地址。虽然比较浪费空间但是可以支持双向遍历,即从某个结点开始既可以往前走也可以往后走比较灵活。从结构上看:双向链表可以支持O(1)时间复杂度的情况下找到前驱结点,使双向链表在某些情况下比单链表的删除,插入更高效。
在实际的软件开发中,从链表中删除一个数据有以下两种情况:
双向循环链表就是将双向链表和循环链表相结合,结构如下
不管是指针还是引用存储的都是所指对象的内存地址,通过这个地址就可以找到所指的对象
插入结点的时候要注意操作的顺序,比如要在a,b两个结点之间插入x结点,如果按如下顺序执行
a->next=x;
x->next=a->next;
就会发生错误,在第一步操作之后a结点的下一个指向的就变成了x,在执行第二步时,x指向了自己。就会导致链表从x结点开始发生了中断,没有及时释放内存的话会造成内存泄漏。
删除结点时,要记得手动释放内存空间,否则会出现内存泄露的问题。[对于自动管理内存的语言不需要考虑]
首先回顾一下单链表的插入和删除操作。如果在结点p后面插入一个新的结点,只需要下面两行代码即可
newnode->next = p->next
p->next = newnode
但是,要向一个空链表中插入第一个结点,需要进行一些特殊的处理,其中head表示链表的头结点。
if(head==NULL) {head = newnode;}
从上述代码可以发现,对与单链表的插入操作,第一个结点和其它结点的插入逻辑不一致。接下来看看删除操作。
如果要删除结点p的后继结点,只需要一行代码即可
p->next = p->next->next;
但是,如果要删除链表的最后一个结点,和插入操作一样也需要一些特殊处理。
if(head==p) head=NULL;
从上面的分析可以看出,针对链表的插入和删除操作,插入第一个结点和删除第一个结点需要进行一些特殊的处理,这让代码实现起来很繁琐不简便。
可以通过设置哨兵解决上述的问题。
引入哨兵结点,不论链表是否为空,head指针都会指向这个哨兵结点。通常把带有哨兵结点的链表叫做带头链表,没有哨兵结点的链表叫做不带头链表。这样一来所有的操作都得到了统一。
链表代码编写,需要考虑以下三个边界
题目
将两个升序链表合并为一个新的升序链表并返回。
输入
输入两个整数m,n分别表示两个链表的元素个数。另起两行分别输入第一个和第二个链表中的元素值
输出
输出合并之后链表中的元素
测试样例
样例1
3 3
1 2 4
1 3 4
1 1 2 3 4 4
样例2
0 0
样例3
0 1
0
0
思路:
#include<iostream>
using namespace std;
//list node
struct listnode{
int val;//data
listnode *next;//next pointer
//construct
listnode():val(0),next(nullptr){}
listnode(int v):val(v),next(nullptr){}
listnode(int v,listnode*n):val(v),next(n){}
};
void remove(listnode*,int);
int main(){
//head
listnode *head1 = new listnode();
listnode *head2 = new listnode();
listnode *mv1 = head1;
listnode *mv2 = head2;
int m,n,num;cin>>m>>n;
for(int i=0;i<m;++i){
cin>>num;
listnode *node = new listnode(num);
node->next = mv1->next;
mv1->next=node;
mv1=node;
}
for(int i=0;i<n;++i){
cin>>num;
listnode *node = new listnode(num);
node->next = mv2->next;
mv2->next=node;
mv2=node;
}
//merge
listnode *head = new listnode();
listnode *mv=head;
mv1=head1->next,mv2=head2->next;
while(mv1&&mv2){
if(mv1->val<mv2->val){
mv->next = mv1;
mv1=mv1->next;
}else{
mv->next=mv2;
mv2=mv2->next;
}
mv=mv->next;
}
if(mv1) mv->next=mv1;
else if(mv2) mv->next=mv2;
//print mergelist
mv=head->next;
while(mv) {
cout<<mv->val<<" ";
mv=mv->next;
}
}
package main
import "fmt"
type listnode struct{
val int
next *listnode
}
func newlistnode()*listnode{
return new(listnode)
}
func newlistnode1(num int)*listnode{
node:= new(listnode)
node.val=num
return node
}
func newlistnode2(num int,n *listnode)*listnode{
node:= new(listnode)
node.val=num
node.next=n
return node
}
func main(){
//head
head1:=newlistnode()
head2:=newlistnode()
mv1:=head1
mv2:=head2
var m,n,num int
fmt.Scan(&m,&n)
for i:=0;i<m;i++{
fmt.Scan(&num)
node:=newlistnode1(num)
node.next=mv1.next
mv1.next=node
mv1=node
}
for i:=0;i<n;i++{
fmt.Scan(&num)
node:=newlistnode1(num)
node.next=mv2.next
mv2.next=node
mv2=node
}
//merge list
head:=newlistnode()
mv:=head
mv1,mv2=head1.next,head2.next
for mv1!=nil&&mv2!=nil{
if mv1.val<mv2.val{
mv.next=mv1
mv1=mv1.next
}else{
mv.next=mv2
mv2=mv2.next
}
mv=mv.next
}
if mv1!=nil{
mv.next=mv1
}else if mv2!=nil{
mv.next=mv2
}
//print list
mv=head.next
for mv!=nil {
fmt.Print(mv.val," ")
mv=mv.next
}
}
题目
用链表存储一些整数,给定一个整数val,删除链表中结点值等于给定数值val的所有结点,并输出留下的数据值。
1<=node.val<=50
0<=val<=50
输入
输入整数n和val,分别表示元素的个数和删除的目标值。另起一行输入n个整数值
输出
分别输出删除特定值之前和删除特定值之后留下的数据值
测试样例
样例1
7 6
1 2 6 3 4 5 6
1 2 6 3 4 5 6
1 2 3 4 5
样例2
0 1
样例3
4 7
7 7 7 7
7 7 7 7
思路:
#include<iostream>
using namespace std;
//list node
struct listnode{
int val;//data
listnode *next;//next pointer
//construct
listnode():val(0),next(nullptr){}
listnode(int v):val(v),next(nullptr){}
listnode(int v,listnode*n):val(v),next(n){}
};
void remove(listnode*,int);
int main(){
//head
listnode *head = new listnode();
listnode *mv = head;
int n,val,num;cin>>n>>val;
for(int i=0;i<n;++i){
cin>>num;
listnode *node = new listnode(num);
node->next = mv->next;
mv->next=node;
mv=node;
}
//print list before remove
mv=head->next;
while(mv) {cout<<mv->val<<" ";mv=mv->next;}
cout<<endl;
remove(head,val);
//print list after remove
mv=head->next;
while(mv) {cout<<mv->val<<" ";mv=mv->next;}
//delete node
mv=head;
while(mv) {delete mv;mv=mv->next;}
return 0;
}
void remove(listnode *head,int val){
listnode *mv=head;
while(mv->next){
if(mv->next->val==val) {
listnode *te=mv->next;
mv->next = te->next;
te->next=nullptr;
delete te;
}else {
mv=mv->next;
}
}
}
package main
import "fmt"
type listnode struct{
val int
next *listnode
}
func newlistnode()*listnode{
return new(listnode)
}
func newlistnode1(num int)*listnode{
node:= new(listnode)
node.val=num
return node
}
func newlistnode2(num int,n *listnode)*listnode{
node:= new(listnode)
node.val=num
node.next=n
return node
}
func main(){
//head
head:=newlistnode()
mv:=head
var n,val,num int
fmt.Scan(&n,&val)
for i:=0;i<n;i++{
fmt.Scan(&num)
node:=newlistnode1(num)
node.next=mv.next
mv.next=node
mv=node
}
//print list before remove
mv=head.next
for mv!=nil {
fmt.Print(mv.val," ")
mv=mv.next
}
fmt.Println()
remove(head,val)
//print list after remove
mv=head.next
for mv!=nil {
fmt.Print(mv.val," ")
mv=mv.next
}
}
func remove(head *listnode,val int){
mv:=head
for mv.next!=nil{
if mv.next.val==val{
te:=mv.next
mv.next=te.next
te.next=nil
}else {
mv=mv.next
}
}
}
题目
反转给定的链表
输入
输入一个整数n表示链表的元素个数,另起一行输入n个元素
输出
输出反转前后的链表元素值
测试样例
样例1
5
1 2 3 4 5
1 2 3 4 5
5 4 3 2 1
样例2
2
1 2
1 2
2 1
样例3
0
思路:
迭代
#include<iostream>
using namespace std;
//list node
struct listnode{
int val;//data
listnode *next;//next pointer
//construct
listnode():val(0),next(nullptr){}
listnode(int v):val(v),next(nullptr){}
listnode(int v,listnode*n):val(v),next(n){}
};
listnode* reverse(listnode*);
listnode* reverse1(listnode*);
int main(){
//head
listnode *head=nullptr,*mv=nullptr;
int n,num;cin>>n;
for(int i=0;i<n;++i){
cin>>num;
listnode *node = new listnode(num);
if(head==nullptr){ head=node;}
else{
node->next = mv->next;
mv->next=node;
}
mv=node;
}
//print list before reserve
mv=head;
while(mv) {cout<<mv->val<<" ";mv=mv->next;}
cout<<endl;
listnode *newhead = reverse(head);
//print list after reserve
mv=newhead;
while(mv) {cout<<mv->val<<" ";mv=mv->next;}
//delete node
mv=newhead;
while(mv) {delete mv;mv=mv->next;}
return 0;
}
listnode* reverse(listnode *head){
//diedai
listnode *mv=head,*pre=nullptr;
while(mv->next){
listnode *nextnode = mv->next;
mv->next=pre;
pre=mv;
mv=nextnode;
}
mv->next = pre;
return mv;
}
package main
import "fmt"
type listnode struct{
val int
next *listnode
}
func newlistnode()*listnode{
return new(listnode)
}
func newlistnode1(num int)*listnode{
node:= new(listnode)
node.val=num
return node
}
func newlistnode2(num int,n *listnode)*listnode{
node:= new(listnode)
node.val=num
node.next=n
return node
}
func main(){
//head
var head,mv *listnode
var n,num int
fmt.Scan(&n)
for i:=0;i<n;i++{
fmt.Scan(&num)
node:=newlistnode1(num)
if head==nil{
head=node
}else{
node.next=mv.next
mv.next=node
}
mv=node
}
//print list before reverse
mv=head
for mv!=nil {
fmt.Print(mv.val," ")
mv=mv.next
}
fmt.Println()
newhead:=reverse1(head)
//print list after reverse
mv=newhead
for mv!=nil {
fmt.Print(mv.val," ")
mv=mv.next
}
}
func reverse(head *listnode)*listnode{
//deidai
var pre *listnode
for head.next!=nil{
nextnode:=head.next
head.next=pre
pre=head
head=nextnode
}
head.next=pre
return head
}
递归
listnode* reverse1(listnode*head){
if(!head || !head->next) return head;
listnode *newhead = reverse1(head->next);
head->next->next = head;
head->next=nullptr;
return newhead;
}
func reverse1(node *listnode)*listnode{
if node==nil || node.next==nil{
return node
}
head:=reverse(node.next)
node.next.next=node
node.next=nil
return head
}
题目
两两交换链表中相邻的结点,并返回交换之后链表的头结点。[只能进行结点交换,不能修改结点的内部值]
链表中结点的数目在范围[0,100]内
0<=node.val<=100
/输入
输入一个整数n表示链表的元素个数,另起一行输入n个元素
输出
两两交换前后的链表元素
测试样例
样例1
4
1 2 3 4
1 2 3 4
2 1 4 3
样例2
0
样例3
1
1
1
思路:
#include<iostream>
using namespace std;
//list node
struct listnode{
int val;//data
listnode *next;//next pointer
//construct
listnode():val(0),next(nullptr){}
listnode(int v):val(v),next(nullptr){}
listnode(int v,listnode*n):val(v),next(n){}
};
listnode* swap(listnode*);
listnode* swap1(listnode*);
int main(){
//head
listnode *head=nullptr,*mv=nullptr;
int n,num;cin>>n;
for(int i=0;i<n;++i){
cin>>num;
listnode *node = new listnode(num);
if(head==nullptr) head=node;
else{
node->next = mv->next;
mv->next=node;
}
mv=node;
}
//print list before swap
mv=head;
while(mv) {cout<<mv->val<<" ";mv=mv->next;}
cout<<endl;
listnode *newhead = swap(head);
//print list after swap
mv=newhead;
while(mv) {cout<<mv->val<<" ";mv=mv->next;}
//delete node
mv=newhead;
while(mv) {delete mv;mv=mv->next;}
return 0;
}
listnode* swap(listnode *head){
if(!head || !head->next) return head;
listnode *newhead = head->next;
//diedai
while(head&&head->next){
listnode *node=head->next->next;
head->next->next=head;
if(node&&node->next) head->next=node->next;
else head->next = node;
head=node;
}
return newhead;
}
package main
import "fmt"
type listnode struct{
val int
next *listnode
}
func newlistnode()*listnode{
return new(listnode)
}
func newlistnode1(num int)*listnode{
node:= new(listnode)
node.val=num
return node
}
func newlistnode2(num int,n *listnode)*listnode{
node:= new(listnode)
node.val=num
node.next=n
return node
}
func main(){
//head
var head,mv *listnode
var n,num int
fmt.Scan(&n)
for i:=0;i<n;i++{
fmt.Scan(&num)
node:=newlistnode1(num)
if head==nil{
head=node
}else{
node.next=mv.next
mv.next=node
}
mv=node
}
//print list before swap
mv=head
for mv!=nil {
fmt.Print(mv.val," ")
mv=mv.next
}
fmt.Println()
newhead:=swap(head)
//print list after swap
mv=newhead
for mv!=nil {
fmt.Print(mv.val," ")
mv=mv.next
}
}
func swap(head *listnode)*listnode{
if head==nil || head.next==nil {return head}
newhead:=head.next
for head!=nil&&head.next!=nil{
node:=head.next.next
head.next.next=head
if node!=nil&&node.next!=nil {
head.next=node.next
}else {
head.next=node
}
head=node
}
return newhead
}
题目
删除链表中倒数第N个结点
输入
输入整数m和n分别表示链表的元素个数和删除的结点位置[倒数],另起一行输入m个链表元素
输出
输出删除倒数第n个结点前后,链表中的元素
测试样例
样例1
5 2
1 2 3 4 5
1 2 3 4 5
1 2 3 5
样例2
1 1
1
1
样例2
2 1
1 2
1 2
1
思路:
#include<iostream>
using namespace std;
//list node
struct listnode{
int val;//data
listnode *next;//next pointer
//construct
listnode():val(0),next(nullptr){}
listnode(int v):val(v),next(nullptr){}
listnode(int v,listnode*n):val(v),next(n){}
};
int main(){
//head
listnode *head=new listnode();
listnode *mv=head;
int m,n,num;cin>>m>>n;
for(int i=0;i<m;++i){
cin>>num;
listnode *node = new listnode(num);
node->next = mv->next;
mv->next=node;
mv=node;
}
//print list before delete
mv=head->next;
while(mv) {cout<<mv->val<<" ";mv=mv->next;}
cout<<endl;
{
//delete node
listnode *slow=head,*fast=head;
while(n--) fast=fast->next;
while(fast->next){
fast=fast->next;
slow=slow->next;
}
listnode *te = slow->next;
slow->next = te->next;
te->next=nullptr;
delete te;
}
//print list after delete
mv=head->next;
while(mv) {cout<<mv->val<<" ";mv=mv->next;}
//delete node
mv=head;
while(mv) {delete mv;mv=mv->next;}
return 0;
}
package main
import "fmt"
type listnode struct{
val int
next *listnode
}
func newlistnode()*listnode{
return new(listnode)
}
func newlistnode1(num int)*listnode{
node:= new(listnode)
node.val=num
return node
}
func newlistnode2(num int,n *listnode)*listnode{
node:= new(listnode)
node.val=num
node.next=n
return node
}
func main(){
//head
head:=newlistnode()
mv:=head
var m,n,num int
fmt.Scan(&m,&n)
for i:=0;i<m;i++{
fmt.Scan(&num)
node:=newlistnode1(num)
node.next=mv.next
mv.next=node
mv=node
}
//print list before delete
mv=head.next
for mv!=nil {
fmt.Print(mv.val," ")
mv=mv.next
}
fmt.Println()
{
//delete node
fast,slow:=head,head
for n>0{
fast=fast.next
n--
}
for fast.next!=nil{
fast=fast.next
slow=slow.next
}
slow.next = slow.next.next
}
//print list after delete
mv=head.next
for mv!=nil {
fmt.Print(mv.val," ")
mv=mv.next
}
}
题目
判断一个链表是否是回文链表
输入
输入一个整数n表示链表的元素个数,另起一行输入n个元素表示链表各结点的数值。
输出
若链表是回文链表则输出"YES",否则输出"NO"
测试样例
样例1
6
1 2 3 3 2 1
YES
样例2
2
1 2
NO
思路:
相比较来说还是第三种思路最简洁和容易理解,对于第二种方法我们在遍历的过程中还要记录当前结点是第几个结点,并且要多次遍历链表找到结点对应的倒数结点。
第三种有一个小细节需要注意,要考虑链表的元素个数的奇偶性[由于我们在输入数据的时候输入了链表的元素个数所以就不需要再次遍历链表获取元素总数了]
这里采用第三种思路编写程序
#include<iostream>
using namespace std;
//list node
struct listnode{
int val;//data
listnode *next;//next pointer
//construct
listnode():val(0),next(nullptr){}
listnode(int v):val(v),next(nullptr){}
listnode(int v,listnode*n):val(v),next(n){}
};
int main(){
//head
listnode *head=nullptr,*mv=nullptr;
int n,num;cin>>n;
for(int i=0;i<n;++i){
cin>>num;
listnode *node = new listnode(num);
if(!head) head=node;
else{
node->next = mv->next;
mv->next=node;
}
mv=node;
}
// find middle node and reverse list
listnode *fast=head,*slow=head,*pre=nullptr;
while(fast&&fast->next){
fast=fast->next->next;
//reverse
listnode *te = slow->next;
slow->next=pre;
pre=slow;
slow=te;
}
listnode *h1 = pre,*h2=nullptr;
//odd
if(n&1){
//1 2 3 slow point to 2
h2 = slow->next;
}else{
//1 2 3 4 slow point to 3
h2 = slow;
}
while(h1){
if(h1->val!=h2->val) {cout<<"NO"<<endl;return 0;}
h1=h1->next;
h2=h2->next;
}
cout<<"YES"<<endl;
return 0;
}
package main
import "fmt"
type listnode struct{
val int
next *listnode
}
func newlistnode()*listnode{
return new(listnode)
}
func newlistnode1(num int)*listnode{
node:= new(listnode)
node.val=num
return node
}
func newlistnode2(num int,n *listnode)*listnode{
node:= new(listnode)
node.val=num
node.next=n
return node
}
func main(){
//head
var head,mv *listnode
var n,num int
fmt.Scan(&n)
for i:=0;i<n;i++{
fmt.Scan(&num)
node:=newlistnode1(num)
if head==nil {
head=node
}else{
node.next=mv.next
mv.next=node
}
mv=node
}
//find middle node and reverse list
fast,slow:=head,head
var pre *listnode
for fast!=nil&&fast.next!=nil{
fast=fast.next.next
//reverse
te:=slow.next
slow.next=pre
pre=slow
slow=te
}
//odd
var h1,h2 *listnode
h1=pre
if n&1==1{
//1 2 3 slow point to 2
h2=slow.next
}else{
//1 2 3 4 slow point to 3
h2=slow
}
for h1!=nil{
if h1.val!=h2.val{
fmt.Println("NO")
return
}
h1,h2=h1.next,h2.next
}
fmt.Println("YES")
}
题目:
给定一个链表。如果链表存在环的话,返回开始入环的第一个结点;若不存在环的话返回null。
环的定义:如果链表中有某个结点,可以通过连续跟踪next指针再次到达,链表中存在环。
输入
输入整数n和pos分别表示链表元素个数和环的位置,如果pos为-1则链表不存在环
输出
判断链表是否有环,有的话输出环的入口位置对应的数值,没有的话输出-1
测试样例
样例1
4 1
3 2 0 -4
返回索引为1的链表结点2
样例2
2 0
1 2
返回索引为0的链表结点1
样例3
1 -1
1
-1
思路:
hashmap
#include<iostream>
#include<unordered_set>
using namespace std;
//list node
struct listnode{
int val;//data
listnode *next;//next pointer
//construct
listnode():val(0),next(nullptr){}
listnode(int v):val(v),next(nullptr){}
listnode(int v,listnode*n):val(v),next(n){}
};
listnode *detectCycle(listnode *head);
int main(){
//head
listnode *head=nullptr,*mv=nullptr,*te=nullptr;
int n,pos,num;cin>>n>>pos;
for(int i=0;i<n;++i){
cin>>num;
listnode *node = new listnode(num);
if(!head) head=node;
else{
node->next = mv->next;
mv->next=node;
}
mv=node;
}
if(pos>=0){
te = head;
while(pos--) te=te->next;
mv->next = te;
}
listnode *node = detectCycle(head);
if(node) {
cout<<node->val<<endl;
}else{
cout<<-1<<endl;
}
return 0;
}
listnode *detectCycle(listnode *head){
unordered_set< listnode* > se;
while(head){
if(se.count(head)) return head;
se.insert(head);
head=head->next;
}
return nullptr;
}
package main
import "fmt"
type listnode struct{
val int
next *listnode
}
func newlistnode()*listnode{
return new(listnode)
}
func newlistnode1(num int)*listnode{
node:= new(listnode)
node.val=num
return node
}
func newlistnode2(num int,n *listnode)*listnode{
node:= new(listnode)
node.val=num
node.next=n
return node
}
func main(){
//head
var head,mv,te *listnode
var n,pos,num int
fmt.Scan(&n,&pos)
for i:=0;i<n;i++{
fmt.Scan(&num)
node:=newlistnode1(num)
if head==nil{
head=node
}else{
node.next=mv.next
mv.next=node
}
mv=node
}
if pos>=0{
te=head
for pos>0{
pos--
te=te.next
}
}
mv.next=te
node:=detectCycle(head)
if node!=nil{
fmt.Println(node.val)
}else{
fmt.Println(-1)
}
}
func detectCycle(head *listnode)*listnode{
se:=map[*listnode]struct{}{}
for head!=nil{
if _,ok:=se[head];ok{
return head
}
se[head]=struct{}{}
head=head.next
}
return nil
}
fast-slow
//double pointer
listnode* detectCycle1(listnode*head){
//fast_slow pointer
listnode *fast=head,*slow=head;
while(fast&&fast->next){
fast=fast->next->next;
slow=slow->next;
//先判断是否有环
if(fast==slow){
//确定有环之后,找出环的入口地址
slow=head;
while(slow!=fast){
slow=slow->next;
fast=fast->next;
}
return slow;
}
}
return nullptr;
}
//double pointer
func detectCycle1(head *listnode)*listnode{
fast,slow:=head,head
for fast!=nil&&fast.next!=nil{
fast=fast.next.next
slow=slow.next
if fast==slow{
slow=head
for slow!=fast{
slow,fast=slow.next,fast.next
}
return slow
}
}
return nil
}
时间复杂度 | 数组 | 链表 |
---|---|---|
插入删除 | O(n) | O(1) |
随机访问 | O(1) | O(n) |
数组和链表的对比不能只局限于时间复杂度;在实际的软件开发中,不能仅仅利用复杂度分析就决定使用哪个数据结构存储数据。
fmt.Scan和fmt.Scanf是Go中用于从标准输入读取数据的函数。区别在于参数的使用和具体的输入格式。
fmt.Scan函数使用空格作为分隔符,按照顺序读取输入的值并将其存储在提供的参数中,会自动识别不同类型的数据。得到的输入值会自动去除换行符。
package main
import "fmt"
func main(){
var str,str1 string
var n int
fmt.Scan(&str,&str1)
fmt.Scan(&n)
fmt.Println(str,str1,n)
}
fmt.Scanf函数允许指定具体的格式字符串读取输入。需要提供一个或者多个格式说明符指定输入值的类型。如果格式字符不包含换行符输入中的回车会被当作普通字符处理。解决方式就是在格式字符串中显示添加\n读取并处理换行符
package main
import "fmt"
func main(){
var str,str1 string
var n int
fmt.Scanf("%s%s",&str,&str1)
fmt.Scan("%d",&n)
fmt.Println(str,str1,n)
}
通过运行结果可以看出,Scanf将换行作为普通字符处理;解决方法显示加入\n格式字符
package main
import "fmt"
func main(){
var str,str1 string
var n int
fmt.Scanf("%s%s\n",&str,&str1)
fmt.Scanf("%d",&n)
fmt.Println(str,str1,n)
}
如何读入带有空格的字符串
package main
import (
"fmt"
"bufio"
"os"
"strings"
"reflect"
)
func main(){
//第一种方法得到的是[]string
scanner := bufio.NewScanner(os.Stdin)
scanner.Scan()
input:=scanner.Text()
words:=strings.Fields(input)
fmt.Println(words,getType(words))
for _,str:=range words{
fmt.Printf("%s\t",str)
}
fmt.Println()
//第二种方法得到的是string
reader:=bufio.NewReader(os.Stdin)
name,_:=reader.ReadString('\n')
name=name[:len(name)-1]
fmt.Println(name)
fmt.Println(getType(name))
}
//利用反射获取元素的类型
func getType(v interface{})reflect.Type{
return reflect.TypeOf(v)
}