第二章 基础数据结构
资料来源:尚硅谷java数据结构,程序设计竞赛挑战教程(蓝桥杯专题组编写),CSDN优秀博客整理。
线性表
2.1.4单向环形链表
Josephu(约瑟夫)问题
Josephu问题为:设编号为1,2,…,n的n个人围成一圈,约定编号为k(1<=k<=n)的从1开始报数,数到m的那个人出列,他的下一人又从1开始报数,数到m的人又出列。以此类推,直到所有人出列为止,由此产生一个出列编号的序列。
用一个不带头结点的循环链表来处理Josephu问题,先构成一个有n个结点的单循环的链表,然后由k结点从1开始计数,记到m时,对应节点从链表中删除,然后再从被删除的节点的下一个结点又从1开始计数,知道最后一节点从链表中删除为止删除算法结束。
构建一个单向的环形链表思路:
1、先创建第一个结点,让first指向该节点,并形成环形;
2、创建一个辅助节点cur,用于指向新添加的进去的节点;
3、当我们每创建一个新的节点,就把该节点加入到已有的环形链表中即可。
//创建一个环形单向链表
class CircleSingleLinkedList{
//创建头节点,编号为-1
private Boy first = new Boy(-1);
public void add(int num) {
//判断num是否合理,num应大于1
if(num<1) {
System.out.println("参与的小孩个数应大于1");
return;
}
//创建一个辅助指针cur,帮助我们建立环形单向链表
Boy cur = null;
//通过遍历建立单向环形链表
for(int i = 1;i <= num;i++) {
Boy boy = new Boy(i);
if(i==1) {
first = boy;
boy.setNext(first);//只有一个节点时形成自回环
cur = first;
}else {
cur.setNext(boy);
boy.setNext(first);
cur = boy;
}
}
}
遍历环形链表思路:
1、先创建一个辅助指针(变量)curBoy,让它指向first;
2、通过一个while循环进行遍历,结束条件:curBoy.next == first。
//遍历单向环形链表
public void list() {
//判断是否为空
if(first == null) {
System.out.println("小孩个数为0");
}
Boy cur = first;
while (true) {
System.out.printf("这是第 %d 个小孩\n",cur.getNo());
if(cur.getNext()==first) {
break;//已遍历完成
}
cur = cur.getNext();//指向下一个节点
}
}
输出小孩出圈的顺序序列(默认从1开始报数)思路:
1、创建一个辅助指针(变量)helper,指向环形链表的最后一个节点;
前提:先让first和helper移动到第k个报数的小孩处
2、当该小孩报数时,再让first和helper同时向前移动m-1次;
3、这时first指向的小孩出圈
first = first.getNext();
helper.getNext() = first;
原来first指向的节点就没有任何引用,被回收。
//根据用户输入,输出小孩出圈顺序序列
/**
*
* @param startNum 开始小孩的编号k
* @param countNum 表示数几下
* @param num 参与小孩的总数
*/
public void CountBoy(int startNum, int countNum, int num) {
//进行数据校验:单向环形链表是否为空 开始报数的小孩编号是否大于总数,小于1
if(first == null || startNum < 1 || startNum > num) {
System.out.println("输入的参数不合理,请重新输入~");
return;
}
//定义一个辅助指针helper,并指向最后一个孩子
Boy helper = first.getNext();
while (helper.getNext()!= first) {
helper = helper.getNext();
}
//将first和helper指向第startNum个孩子
while (true) {
if(first.getNo() == startNum) {//成功指向啦
break;
}
first = first.getNext();
helper = helper.getNext();
}
//开始输出小孩出圈顺序
while (true) {
if(helper == first) {//此时圈内只有一个孩子啦
break;
}
for(int i = 0; i<countNum-1; i++) {
first= first.getNext();
helper= helper.getNext();
}
System.out.printf("小孩%d出圈啦\n",first.getNo());
//删除已出圈的小孩
first = first.getNext();
helper.setNext(first);
}
System.out.printf("圈内最后留下的小孩%d\n", first.getNo());
}
}
完整代码展示
public class Josephu {
public static void main(String[] args) {
// TODO 自动生成的方法存根
CircleSingleLinkedList circleSingleLinkedList = new CircleSingleLinkedList();
circleSingleLinkedList.add(5);
circleSingleLinkedList.list();
//测试一下
circleSingleLinkedList.CountBoy(1, 2, 5);//应输出2,4,1,5,3
}
}
//创建一个环形单向链表
class CircleSingleLinkedList{
//创建头节点,编号为-1
private Boy first = new Boy(-1);
public void add(int num) {
//判断num是否合理,num应大于1
if(num<1) {
System.out.println("参与的小孩个数应大于1");
return;
}
//创建一个辅助指针cur,帮助我们建立环形单向链表
Boy cur = null;
//通过遍历建立单向环形链表
for(int i = 1;i <= num;i++) {
Boy boy = new Boy(i);
if(i==1) {
first = boy;
boy.setNext(first);//只有一个节点时形成自回环
cur = first;
}else {
cur.setNext(boy);
boy.setNext(first);
cur = boy;
}
}
}
//遍历单向环形链表
public void list() {
//判断是否为空
if(first == null) {
System.out.println("小孩个数为0");
}
Boy cur = first;
while (true) {
System.out.printf("这是第 %d 个小孩\n",cur.getNo());
if(cur.getNext()==first) {
break;//已遍历完成
}
cur = cur.getNext();//指向下一个节点
}
}
//根据用户输入,输出小孩出圈顺序序列
/**
*
* @param startNum 开始小孩的编号k
* @param countNum 表示数几下
* @param num 参与小孩的总数
*/
public void CountBoy(int startNum, int countNum, int num) {
//进行数据校验:单向环形链表是否为空 开始报数的小孩编号是否大于总数,小于1
if(first == null || startNum < 1 || startNum > num) {
System.out.println("输入的参数不合理,请重新输入~");
return;
}
//定义一个辅助指针helper,并指向最后一个孩子
Boy helper = first.getNext();
while (helper.getNext()!= first) {
helper = helper.getNext();
}
//将first和helper指向第startNum个孩子
while (true) {
if(first.getNo() == startNum) {//成功指向啦
break;
}
first = first.getNext();
helper = helper.getNext();
}
//开始输出小孩出圈顺序
while (true) {
if(helper == first) {//此时圈内只有一个孩子啦
break;
}
for(int i = 0; i<countNum-1; i++) {
first= first.getNext();
helper= helper.getNext();
}
System.out.printf("小孩%d出圈啦\n",first.getNo());
//删除已出圈的小孩
first = first.getNext();
helper.setNext(first);
}
System.out.printf("圈内最后留下的小孩%d\n", first.getNo());
}
}
class Boy{
private int no;
private Boy next;
public Boy(int no) {
this.no = no;
}
public void setNo(int no) {
this.no = no;
}
public int getNo() {
return no;
}
public void setNext(Boy next) {
this.next = next;
}
public Boy getNext() {
return next;
}
}
2.1.5 历年真题
自行车停放(1518)
【题目描述】有n辆自行车依次来到停车棚,除了第一辆自行车外,每辆自行车都会恰好停放在已经在停车棚里的某辆自行车的左边或右边。例如,停车棚里已经有3辆自行车,从左到右编号为3、5、1,现在编号为2的第4辆自行车要停放在编号为5的自行车的左边,停车棚里的自行车编号就会变为3、2、5、1。给定n辆自行车的停放情况,按顺序输出最后停放在车棚里的自行车编号。n≤100000。
【输入描述】第一行输入一个整数n。第二行输入一个整数x,表示第一辆自行车的编号。以下 n−1行,每行输入 3个整数 x、y、z。 z = 0时,表示编号为 x 的自行车恰好停放在编号为 y 的自行车的左边。z = 1时,表示编号为 x 的自行车恰好停放在编号为y的自行车的右边。
【输出描述】从左到右输出停车棚里的自行车编号。
【输入样例】4
3
1 3 1
2 1 0
5 2 1
【输出样例】3 2 5 1
数据输入
第一种:一行输入多个以空格分割的数字
我们将1 3 1整体看成一个字符串,然后在空格处分割,分成1,3和3三个字符串,在将这两个字符串转换成整型就可以了。
//输入多个数值,用空格隔开的情况
for(int i = 0; i<n-1; i++) {
String[] str = in.nextLine().split("\\s+");//分成几块,就有几个字符串数组,这里是三块
int x = Integer.parseInt(str[0]);
int y = Integer.parseInt(str[1]);
int z = Integer.parseInt(str[2]);;
sL.add(x, y, z);
}
此处还会出现另一个问题,由于前面需要输入总数n和第一个自行车的编号,我们用回车来结束输入,在缓存区还会有一个回车,jdk会将一个空字符串赋给str,报错如下:
所有从键盘输入的数据,不管是字符还是数字,都是先存储在内存的缓冲区中,叫作“键盘输入缓冲区”,简称“输入缓冲区”或“输入流”。我们先来看一个程序:
int a,b,c;
a = in.nextInt();
System.out.println(a);
b = in.nextInt();
System.out.println(b);
c = in.nextInt();
System.out.println(c);
//此处\n代表的时,键盘上按回车和屏幕上显示换行
//输入:1 2 3和1\n2\n3\n
//输出都为:1\n2\n3\n
从输出结果可以看出,不管是一个一个地输入:1(回车)2(回车)3(回车);还是三个数字一次性输入:1(空格)2(空格)3(回车),这两种输入方法的结果都是一样的。原因是从键盘输入的数据都会被依次存入缓冲区,不管是数字还是字符都会被当成数据存进去。但只有按回车, 才会进去取数据,所取数据的个数取决于 sc 中接收参数的个数。因此不在于怎么输入,可以存一个取一个,也可以一次性全存入进去,然后一个个取。
所以我们要先用scanner.nextLine()来接收缓存区遗留的回车。
public static void main(String[] arge) {
Scanner in = new Scanner(System.in);
int n = in.nextInt();
singleLinkedL sL = new singleLinkedL();
int x1 = in.nextInt();
sL.add(x1);
in.nextLine();
for(int i = 0; i<n-1; i++) {
String[] str = in.nextLine().split("\\s+");//分成几块,就有几个字符串数组,这里是三块
int x = Integer.parseInt(str[0]);
int y = Integer.parseInt(str[1]);
int z = Integer.parseInt(str[2]);;
sL.add(x, y, z);
}
sL.list();
}
第二种:使用简单的int格式输入
每一行只有一个int型格式的输入的情况。比如,每一行只输入一个整型的情况。
public static void main(String[] arge) {
Scanner in = new Scanner(System.in);
int n = in.nextInt();
singleLinkedL sL = new singleLinkedL();
int x1 = in.nextInt();
sL.add(x1);
for(int i = 0; i<n-1; i++) {
int x = in.nextInt();
int y = in.nextInt();
int z = in.nextInt();
sL.add(x, y, z);
}
sL.list();
}
补充:第三种复杂带int的格式输入
原文来源
类似于输入a=3,b=2这样的,方法和第二种中说明的情况是一样的。例题如下:
描述:
输入两个整数,范围-231~231-1,交换两个数并输出
输入描述:
输入只有一行,按照格式输入两个整数,范围,中间用“,”分隔
输出描述:
把两个整数按格式输出,中间用“”分隔
示例1
输入:a=1,b=2
输出:a=2,b=1
备注:
如果格式控制串中有非格式字符则输入时也要输入该非格式字符
public static void main(String[] arge) {
Scanner in = new Scanner(System.in);
String[] line = in.nextLine().split("[,]");
String[] num1 = line[0].split("=");
String[] num2 = line[1].split("=");
System.out.println(num1[0]+"="+num2[1]+","+num2[0]+"="+num1[1]);
}
//输入:a=1,b=2
//输出:a=2,b=1
代码实现
第一种方法:单向链表
创建链表思路:
1、首个自行车节点直接连接在头节点之后;
2、z=0,编号为x 的自行车恰好停放在编号为 y 的自行车的左边,使用前插法,连接在编号为y的节点的前一个节点之后,并指向编号为y的节点;
3、z=1,编号为x 的自行车恰好停放在编号为 y 的自行车的右边,使用后插法,连接在编号为y的节点之后,并让next=null。
完整代码:
import java.util.Scanner;
public class lianxiti {
public static void main(String[] arge) {
Scanner in = new Scanner(System.in);
int n = in.nextInt();
singleLinkedL sL = new singleLinkedL();
int x1 = in.nextInt();
sL.add(x1);
for(int i = 0; i<n-1; i++) {
int x = in.nextInt();
int y = in.nextInt();
int z = in.nextInt();
sL.add(x, y, z);
}
sL.list();
}
}
class singleLinkedL{
//定义头节点
public bikeNum head = new bikeNum(0);
public bikeNum first;
//创建三种添加节点方法,
//第一种:添加第一个节点
public void add(int i) {
bikeNum first = new bikeNum(i);
first.next = head.next;
head.next = first;
}
public void add(int x,int y,int z) {
bikeNum bikeNum = new bikeNum(x);
//先遍历一下找到要插入的节点位置
bikeNum helper = head;
while(true) {
if(helper.next==null){
break;//进行到最后了,或者为空
}
if(helper.next.no == y) {
break;//找到要插入的节点的前一个节点了
}
helper = helper.next;
}
//z = 1将指针指向要插入的节点
if(z==1) {
helper = helper.next;//将指针指向要插入的节点
}
bikeNum.next = helper.next;
helper.next = bikeNum;
}
public void list() {
if(head.next == null) {
System.out.println("链表为空");
return;
}
//头节点不能动,因此我们需要一个辅助变量
bikeNum temp = head.next;
while(true) {
if(temp == null) {
break;
}
//输出节点信息
System.out.printf("%d\t",temp.no);
//节点后移
temp = temp.next;
}
}
}
class bikeNum{
public int no;
public bikeNum next;
//构造器
public bikeNum(int no) {
// TODO 自动生成的构造函数存根
this.no = no;
}
}
第二种方法:双向链表
创建双向链表思路:
1、首个自行车节点直接连接在头节点之后;
2、找到编号为y的节点,通过y节点的pre, next很方便的来访问所需要的指针,优点在于直接查找找到编号为y的节点,不需要考虑前插还是后插。
需要注意以下语句的前后顺序和辅助指针helper.next是否为空(helper首先指向head.next,它的pre指向head,不为空)
helper.next.pre = bikeNum;
bikeNum.pre = helper;
bikeNum.next = helper.next;
helper.next = bikeNum;
完整代码:
import java.util.Scanner;
public class lianxiti {
public static void main(String[] arge) {
Scanner in = new Scanner(System.in);
int n = in.nextInt();
singleLinkedL sL = new singleLinkedL();
int x1 = in.nextInt();
sL.add(x1);
for(int i = 0; i<n-1; i++) {
int x = in.nextInt();
int y = in.nextInt();
int z = in.nextInt();
sL.add(x, y, z);
}
sL.list();
}
}
class singleLinkedL{
//定义头节点
public bikeNum head = new bikeNum(0);
public bikeNum first;
//创建两种添加节点方法,
//第一种:添加第一个节点
public void add(int i) {
bikeNum first = new bikeNum(i);
first.next = head.next;
first.pre = head;
head.next = first;
}
//此处方法重写,通过参数个数的不同来调用不同的函数
public void add(int x,int y,int z) {
bikeNum bikeNum = new bikeNum(x);
//先遍历一下找到要插入的节点位置
bikeNum helper = head;
while(true) {
if(helper.next==null){
break;//进行到最后了,或者为空
}
if(helper.no == y) {
break;//找到要插入的节点了
}
helper = helper.next;
}
if(z==0) {
//z = 1时,表示编号为 x 的自行车恰好停放在编号为 y 的自行车的左边。
helper.pre.next = bikeNum;
bikeNum.pre = helper.pre;
bikeNum.next = helper;
helper.pre = bikeNum;
}else {//z = 1时,表示编号为 x 的自行车恰好停放在编号为y的自行车的右边。
if(helper.next==null) {
bikeNum.pre = helper;
bikeNum.next = helper.next;
helper.next = bikeNum;
}
else {
helper.next.pre = bikeNum;
bikeNum.pre = helper;
bikeNum.next = helper.next;
helper.next = bikeNum;
}
}
}
public void list() {
if(head.next == null) {
System.out.println("链表为空");
return;
}
//头节点不能动,因此我们需要一个辅助变量
bikeNum temp = head.next;
while(true) {
if(temp == null) {
break;
}
//输出节点信息
System.out.printf("%d\t",temp.no);
//节点后移
temp = temp.next;
}
}
}
class bikeNum{
public int no;
public bikeNum next,pre;
//构造器
public bikeNum(int no) {
// TODO 自动生成的构造函数存根
this.no = no;
}
}
小王子与玩具(1110/1112)
【题目描述】
小王子有一天迷上了排队的游戏,桌子上有标号为1-10的10个玩具,现在小王子将他们排成一列,可小王子还是太小了,他不确定他到底想把那个玩具摆在哪里,直到最后才能排成条直线,求玩具的编号。已知他排了 M次,每次都是选取标号为 X个放到最前面,求每次排完后玩具的编号序列。
要求一:采用单链表解决【输入描述】
第一行是一个整数M,表示小王子排玩具的次数
随后M行每行包含一个整数X,表示小王子要把编号为X的玩具放在最前面。【输出描述】
共M行,第行输出小王子第次排完序后玩的编号序列【输入输出样例】
示例一
输入:5 3 2 3 4 2(每个数字独占一行)输出:3 1 2 4 5 6 7 8 9 10
2 3 1 4 5 6 7 8 9 10
3 2 1 4 5 6 7 8 9 10
4 3 2 1 5 6 7 8 9 10
2 4 3 1 5 6 7 8 9 10
代码实现
第一种方式:使用单链表
创建链表思路:
1、使用后插法创建有10个玩具节点的单链表;
2、创建辅助指针helper指向编号为x的节点的前节点,用辅助指针cur保存编号为x的节点添加在head节点之后;
curNum.next = head.next;
head.next = curNum;
3、通过while(temp == null)循环遍历输出结果。
完整代码:
import java.util.Scanner;
//第一行是一个整数M,表示小王子排玩具的次数
//随后M行每行包含一个整数X,表示小王子要把编号为X的玩具放在最前面。
public class lianxiti {
public static void main(String[] arge) {
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
singleLinkedL sL = new singleLinkedL();
sL.add(10);
for(int i = 0; i < n;i++){
int x = sc.nextInt();
sL.add1(x);
sL.list();
}
}
}
class singleLinkedL{
//定义头节点
public toyNum head = new toyNum(0);
toyNum helper = head;//因为头指针不能动,创建辅助指针帮助建立单链表
public void add(int n) {
for(int i=1;i <= n;i++) {
toyNum temp = new toyNum(i);
temp.next = helper.next;
helper.next = temp;
helper= helper.next;
}
}
public void add1(int x) {
if(x <= 1 && 10 <= x) {
return;
}
toyNum helper = head;
while (helper!=null) {//找到要放置到最前边的节点,helper所指的就是它的前一个,方便剪切
if(helper.next.no == x) {
break;
}
helper = helper.next;
}
toyNum curNum = helper.next;//curNum指的就是要放置到最前边的节点
if(helper.next.next != null) {//防止要移动的节点恰好是最后一个节点
helper.next = helper.next.next;
}
else {
helper.next = null;
}
curNum.next = head.next;
head.next = curNum;
}
public void list() {
if(head.next == null) {
System.out.println("链表为空");
return;
}
//头节点不能动,因此我们需要一个辅助变量
toyNum temp = head.next;
while(true) {
if(temp == null) {
break;
}
//输出节点信息
System.out.printf("%d\t",temp.no);
//节点后移
temp = temp.next;
}
System.out.println();
}
}
class toyNum{
public int no;
public toyNum next;
//构造器
public toyNum(int no) {
// TODO 自动生成的构造函数存根
this.no = no;
}
}
第二种方式:使用循环链表
创建链表思路:
1、使用后插法创建有10个玩具节点的循环链表;
2、创建辅助指针helper指向编号为x的节点的前节点,用辅助指针temp指向编号为x的节点,添加在head节点之后;
helper.next = temp.next;//如果是最后一个节点也成立
temp.next = head.next;
head.next = temp;
3、通过while(temp == head)循环遍历输出结果。
完整代码:
import java.util.Scanner;
//第一行是一个整数M,表示小王子排玩具的次数
//随后M行每行包含一个整数X,表示小王子要把编号为X的玩具放在最前面。
public class lianxiti {
public static void main(String[] arge) {
Scanner sc = new Scanner(System.in);
singleLinkedL sL = new singleLinkedL();
sL.add(10);
int n = sc.nextInt();
for(int i = 0; i < n;i++){
int x = sc.nextInt();
sL.add1(x);
sL.list();
}
}
}
class singleLinkedL{
//定义头节点
public toyNum head = new toyNum(0);
toyNum helper = head;//因为头指针不能动,创建辅助指针帮助建立循环链表
//第一种:添加第一个节点
public void add(int n) {
for(int i = 1;i <= n;i++) {
toyNum temp = new toyNum(i);
temp.next = head;
helper.next = temp;
helper= helper.next;
}
}
public void add1(int x) {
if(x <= 1 && 10 <= x) {
return;
}
toyNum helper = head;//helper所指的就是它的前一个,方便剪切
toyNum temp = head.next;//temp所指的就是要放置到最前边的节点
while (temp != head) {//找到要放置到最前边的节点,helper所指的就是它的前一个,方便剪切
if(temp.no == x) {
break;
}
helper = helper.next;
temp = temp.next;
}
helper.next = temp.next;//如果是最后一个节点也成立
temp.next = head.next;
head.next = temp;
}
public void list() {
if(head.next == null) {
System.out.println("链表为空");
return;
}
//头节点不能动,因此我们需要一个辅助变量
toyNum temp = head.next;
while(true) {
if(temp == head) {
break;
}
//输出节点信息
System.out.printf("%d\t",temp.no);
//节点后移
temp = temp.next;
}
System.out.println();
}
}
class toyNum{
public int no;
public toyNum next;
//构造器
public toyNum(int no) {
// TODO 自动生成的构造函数存根
this.no = no;
}
}
整数删除(3515)
【问题描述】
给定一个长度为 N 的整数数列:x1,x2,…,xn。你要重复以下操作K次:
每次选择数列中最小的整数(如果最小值不止一个,选择最靠前的),将其删除。并把与它相邻的整数加上被删除的数值
输出K次操作后的序列。
【输入格式】
第一行包含两个整数N和K
第二行包含N 个整数,A1,A2,A3,…,AN。
【输出格式】
输出NK个整数,中间用一个空格隔开,代表飞次操作后的序列。【样例说明】
代码实现
方式:使用双向链表
创建双向链表思路:
1、使用后插法创建有n个节点的双向链表;
2、创建辅助指针helper/帮助遍历找到最小值的节点,用指针minNum指向最小值节点;
//一般情况
nimNum.pre.vaule += nimNum.vaule;
nimNum.next.vaule += nimNum.vaule;
//删除nimNum指向的最小值节点
nimNum.pre.next = nimNum.next;
nimNum.next.pre = nimNum.pre;
3、通过while(temp == null)循环遍历输出结果。
以下代码只能通过30%的评测数据,要全部通过还需要学习优先队列来优化代码,减少运行时间。等学完之后,更新这道题的代码。
完整代码:
import java.util.Scanner;
//给定一个长度为 N 的整数数列:x1,x2,..,xn。你要重复以下操作K次:
//每次选择数列中最小的整数(如果最小值不止一个,选择最靠前的),将其删除。并把与它相邻的整数加上被删除的数值
//输出K次操作后的序列。
public class lianxiti {
public static void main(String[] arge) {
Scanner scan = new Scanner(System.in);
singleLinkedL sL = new singleLinkedL();
int n = scan.nextInt();//长度为 N 的整数数列
int k = scan.nextInt();//重复以下操作K次
scan.nextLine();//清理输入区的缓存 \\s+将多个空格当作一个空格
String[] myStr = scan.nextLine().split("\\s+");
int[] nums = new int[n];
for(int i = 0; i<n; i++) {
nums[i] = Integer.parseInt(myStr[i]);
}
sL.add(nums);
for(int i =0 ;i < k; i++) {
sL.del();
}
sL.list();
}
}
class singleLinkedL{
//定义头节点
public Num head = new Num(0);
Num helper = head;//因为头指针不能动,创建辅助指针帮助建立双向链表
public void add(int[] nums) {
for(int i =0; i<nums.length;i++) {
Num num = new Num(nums[i]);
num.next = helper.next;
num.pre = helper;
helper.next = num;
helper = helper.next;//忘记这一步的话,就会前插法建立链表
}
}
//删除数列中最小的整数(如果最小值不止一个,选择最靠前的),将其删除。并把与它相邻的整数加上被删除的数值
public void del() {
//链表为空 或者 链表只有一个节点
if(head.next==null || head.next.next == null) {
return;
}
Num helper = head.next;//辅助指针帮助遍历
Num nimNum = helper;//指向最小值节点
while(helper.next!=null) {//nimNum最终指向最小值节点
if(nimNum.vaule > helper.next.vaule) {
nimNum = helper.next;
}
helper = helper.next;
}
if(nimNum.pre == head) {//最小值节点为首元节点时
nimNum.next.vaule += nimNum.vaule;
//删除nimNum指向的最小值节点
nimNum.pre.next = nimNum.next;
nimNum.next.pre = nimNum.pre;
}
else if(nimNum.next == null) {//最小值节点为最后一个节点时
nimNum.pre.vaule+=nimNum.vaule;
//删除nimNum指向的最小值节点
nimNum.pre.next = nimNum.next;
}
else {//其他中间的节点
nimNum.pre.vaule += nimNum.vaule;
nimNum.next.vaule += nimNum.vaule;
//删除nimNum指向的最小值节点
nimNum.pre.next = nimNum.next;
nimNum.next.pre = nimNum.pre;
}
}
public void list() {
if(head.next == null) {
System.out.println("链表为空");
return;
}
//头节点不能动,因此我们需要一个辅助变量
Num temp = head.next;
while(true) {
if(temp == null) {
break;
}
//输出节点信息
System.out.printf("%d\t",temp.vaule);
//节点后移
temp = temp.next;
}
System.out.println();
}
}
class Num{
public int vaule;
public Num next,pre;
//构造器
public Num(int no) {
// TODO 自动生成的构造函数存根
this.vaule = no;
}
}
左移右移(2219)
【问题描述】
小蓝有一个长度为的数组,初始时从左到右依次是1,2,3,…N。
之后小蓝对这个数组进行了M次操作每次操作可能是以下2种之一:
1.左移 ,即把 a 移动到最左边
2.右移a,即把 移动到最右边
请你回答经过M次操作之后,数组从左到右每个数是多少?【输入格式】
第一行包含2个整数N和M
以下M行每行一个操作,其中“La"表示左移a”Ra"表示右移x。【输出格式】
输出N个数代表操作后的数组【样例输入】
5 3
L 3
L 2
R 1
【样例输出】
2 3 4 5 1
字符串比较
Java字符串比较的几种方法:equals() 方法、equalsIgnoreCase() 方法、 compareTo() 方法,“==”等
1、equals()
equals() 方法比较两个字符串的每个字符是否相同。如果两个字符串字符和长度相同,它返回 true,否则返回 false;大小写也会检查!
语法格式如下:
public class lianxiti {
public static void main(String[] args) {
String a1 = "aaa";
String a2 = new String("aaa");
String a3 = "ABC";
System.out.println(a1.equals(a2));
System.out.println(a2.equals(a3));
System.out.println(a1.equals(a3));
}
}
//运行结果:
//true
//false
//false
2、equalsIgnoreCase()
equalsIgnoreCase() 方法的作用和语法与 equals() 方法完全相同,唯一一点不同的是 equalsIgnoreCase() 比较的时候不区分大小写。当比较两个字符串时,它会认为 a-z和A-Z是一样的。
语法格式如下:
public class lianxiti {
public static void main(String[] args) {
String st1 = "abc";
String st2 = "ABC";
System.out.println(st1.equalsIgnoreCase(st2)); // 输出 true
}
}
3、compareTo()
compareTo() 方法用于按字典顺序比较两个字符串的大小,该比较是基于字符串各个字符的 Unicode 值
注:如果两个字符串调用 equals() 方法返回 true,那么调用 compareTo() 方法会返回 0;从头依次比较字符,不同的话,返回不同字符的 Unicode 值的差值(有正有负),如果从头依次比较字符都相同,但字符串长度不同,返回字符串长度的差值(例如:“a”与“aaa”)。
compareToIgnoreCase() 方法的作用和语法与 compareTo() 方法完全相同,唯一一点不同的是 equalsIgnoreCase() 比较的时候不区分大小写。当比较两个字符串时,它会认为 a-z和A-Z是一样的。
public class lianxiti {
public static void main(String[] args) {
String st1 = "abc";
String st2 = "ABC";
String st3 = "abc";
String st4 = "abcd";
System.out.println(st1.compareTo(st2)); // 输出-32
System.out.println(st1.compareTo(st3)); // 输出0
System.out.println(st1.compareTo(st4)); // 输出-1
System.out.println(st1.compareToIgnoreCase(st2)); // 输出0
System.out.println(st1.compareToIgnoreCase(st3)); // 输出0
System.out.println(st1.compareToIgnoreCase(st4)); // 输出-1
}
}
代码实现
第一种方式:使用双向链表
创建双向链表思路:
1、使用后插法创建有n个节点的双向链表;
2、创建辅助指针helper帮助遍历找到值为x的节点,用指针helper指向要移动节点;
3、输入的是L x,我们就找到这个x节点,可以直接将该节点剪切,移动到链表的头部连接;
输入的是R x,我们就找到这个x节点,可以直接将该节点剪切,移动到链表的尾部连接
4、通过while(temp == null)循环遍历输出结果。
以下代码只能通过50%的评测数据,要全部通过还需要学习HashMap+左右临界值实现来优化代码,减少运行时间。
完整代码:
import java.util.Scanner;
//第一行包含2个整数N和M
//以下M行每行一个操作,其中“La"表示左移a”Ra"表示右移x。
//输出N个数代表操作后的数组
//建立双向链表方便解决移到最前和最后的问题
public class lianxiti {
public static void main(String[] arge) {
Scanner scan = new Scanner(System.in);
singleLinkedL sL = new singleLinkedL();
int n = scan.nextInt();//总数
sL.add(n);
int m = scan.nextInt();//移动执行次数
scan.nextLine();//清理输入区的缓存 \\s+将多个空格当作一个空格
for(int i = 0 ; i < m ;i++) {
String[] myStr = scan.nextLine().split("\\s+");
String key = myStr[0];
int x = Integer.parseInt(myStr[1]);
sL.moveNum(key, x);
}
sL.list();
}
}
class singleLinkedL{
//定义头节点
public Num head = new Num(0);
Num helper = head;//因为头指针不能动,创建辅助指针帮助建立双向链表
public void add(int n) {
for(int i = 1;i <= n;i++) {
Num num = new Num(i);
num.next = helper.next;
num.pre = helper;
helper.next = num;
helper = helper.next;//忘记这一步的话,就会前插法建立链表
}
}
public void moveNum(String key, int x) {
if(x <= 1 && 10 <= x) {//判断数据是否合理
return;
}
Num helper = head.next;
while (helper.next != null) {//找到要移动的的节点,helper所指的就是它,方便移动
if(helper.vaule == x) {
break;
}
helper = helper.next;
}
if(key.equals("L")) {//剪切值为x的节点
helper.pre.next = helper.next;
if(helper.next != null) {
helper.next.pre = helper.pre;
}
helper.next = head.next;//将值为x的节点连接在head之后 想象“4”条线
head.next.pre = helper;
helper.pre = head;
head.next = helper;
}
else if(key.equals("R")) {
Num rawNum = helper;//指向链表中最后一个
while (rawNum.next != null) {
rawNum = rawNum.next;
}
if(helper.next == null) {//如果正好是最后一个,不用执行
return;
}
else {//剪切值为x的节点
helper.pre.next = helper.next;
helper.next.pre = helper.pre;
}
helper.next = rawNum.next;//将值为x的节点连接在raw之后 想象“3”条线
rawNum.next = helper;
helper.pre = rawNum;
}
}
public void list() {
if(head.next == null) {
System.out.println("链表为空");
return;
}
//头节点不能动,因此我们需要一个辅助变量
Num temp = head.next;
while(true) {
if(temp == null) {
break;
}
//输出节点信息
System.out.printf("%d\t",temp.vaule);
//节点后移
temp = temp.next;
}
System.out.println();
}
}
class Num{
public int vaule;
public Num next,pre;
//构造器
public Num(int no) {
// TODO 自动生成的构造函数存根
this.vaule = no;
}
}
第二种方式:HashMap+左右临界值
1、使用HashMap存储这n个值,初始化的时候key和value相等,都存的是数值。
定义两个边界,左边界:l=0,有边界:r=n+1;
2、从第一个元素开始遍历,当接收到L x,开始左移的时候,我们的key不动,将value赋值为左边界l,并将左边界自减l–。
当接收到R x,开始右移动的时候,我们同样将key不动,将value赋值为右边界R,同时将右边界的值自增r++。
3、遍历结束之后,我们只需要将map中的值按照value排序,然后输出排序之后的key即可。
import java.util.*;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.*;
public class Main {
static BufferedReader br=new BufferedReader(new InputStreamReader(System.in));
public static void main(String[] args) throws IOException {
String[] num = br.readLine().split(" ");
int n=Integer.parseInt(num[0]);
int m=Integer.parseInt(num[1]);
int l=0,r=n+1;
HashMap<Integer,Integer> map=new HashMap<>();
for (int i=1;i<=n;i++){
map.put(i,i);
}
while (m>0){
String[] s = br.readLine().split(" ");
if (s[0].equals("L")){
map.put(Integer.parseInt(s[1]),l--);//每次左移之后。临界值就要自减。因为后续是根据value值来排序的
}else {
map.put(Integer.parseInt(s[1]),r++);//每次右移之后。临界值就要自增
}
m--;
}
List<Map.Entry<Integer,Integer>> list=new ArrayList<Map.Entry<Integer,Integer>>(map.entrySet());
//把map集合根据value排序
Collections.sort(list, new Comparator<Map.Entry<Integer, Integer>>() {
@Override
public int compare(Map.Entry<Integer, Integer> o1, Map.Entry<Integer, Integer> o2) {
return o1.getValue()-o2.getValue();
}
});
for (Map.Entry<Integer, Integer> mapping : list) {
System.out.print(mapping.getKey()+" ");
}
}
}
下一篇更新内容:队列相关内容