剑指Offer系列之题26~题30
26.复杂链表的复制 🔺
输入一个复杂链表(每个节点中有节点值,以及两个指针,一个指向下一个节点,另一个特殊指针指向任意一个节点),返回结果为复制后复杂链表的head。(注意,输出结果中请不要返回参数中的节点引用,否则判题程序会直接返回空)
1.暴力:
/*
public class RandomListNode {
int label;
RandomListNode next = null;
RandomListNode random = null;
RandomListNode(int label) {
this.label = label;
}
}
*/
public class Solution {
public RandomListNode Clone(RandomListNode pHead)
{
if(pHead==null)
return null;
RandomListNode result=new RandomListNode(pHead.label);
RandomListNode temp=result;
while(pHead!=null){//需要用到末尾节点的属性时,条件判断末尾是否空
temp.next=new RandomListNode(pHead.next.label);
temp=temp.next;
pHead=pHead.next;
}//先将next连接起来
//连接random
temp=result;
RandomListNode temp1=result;
while(pHead!=null){//遍历链表,找到与pHead.random相等的节点,将其链接到复制链表的当前节点
while(pHead.random.label != temp1.label && temp1!=null){
temp1=temp1.next;
}
temp.random=temp1;
temp=temp.next;
temp1=result;
pHead=pHead.next;
}
return result;
}
}
2.三步法:
/*
public class RandomListNode {
int label;
RandomListNode next = null;
RandomListNode random = null;
RandomListNode(int label) {
this.label = label;
}
}
*/
public class Solution {
public RandomListNode Clone(RandomListNode pHead)
{
if(pHead==null)
return null;
RandomListNode pNode=pHead;
//将每个节点的复制链接在原节点之后
//A A' B B' C C'
while(pNode!=null){
RandomListNode temp=new RandomListNode(pNode.label);
temp.next=pNode.next;
temp.random=null;
pNode.next=temp;
pNode=temp.next;
}
pNode=pHead;
//将复制节点的random也连接起来
while(pNode!=null){
RandomListNode pClone=pNode.next;
if(pNode.random!=null){
pClone.random=pNode.random.next;
}
pNode=pClone.next;
}
//拆分链表
RandomListNode result=pHead.next;
RandomListNode resClone=result;
pNode=pHead;
while(pNode!=null){
pNode.next=resClone.next;//断开原节点与复制节点的连接
if(pNode.next!=null)//当复制节点不到末尾时
resClone.next=pNode.next.next;//断开复制节点与原节点的连接
pNode=pNode.next;
resClone=resClone.next;
}
return result;
}
}
27.二叉搜索树与双向链表
输入一棵二叉搜索树,将该二叉搜索树转换成一个排序的双向链表。要求不能创建任何新的结点,只能调整树中结点指针的指向。
设链表的尾节点,当左子树空,则连接尾节点与根节点后更新尾节点。直到最后,尾节点是二叉树中的最大值。
1.递归(中序遍历):
/**
public class TreeNode {
int val = 0;
TreeNode left = null;
TreeNode right = null;
public TreeNode(int val) {
this.val = val;
}
}
*/
public class Solution {
public TreeNode Convert(TreeNode pRootOfTree) {
TreeNode lastNode=null;//链表末尾节点
TreeNode result=helper(pRootOfTree,lastNode);
while(result!=null && result.left!=null)
result=result.left;
return result;
}
public TreeNode helper(TreeNode root,TreeNode lastNode){
//递归
if(root==null)
return null;
//当节点的左右子树非空,节点的left指针为左子树,right指针为右子树。
//当节点的左子树为空,将其连接到lastNode
if(root.left!=null){
lastNode=helper(root.left,lastNode);
}
root.left=lastNode;
if(lastNode!=null)
lastNode.right=root;//最小值链接链表末尾节点
lastNode=root;//然后更新末尾节点
if(root.right!=null){
lastNode=helper(root.right,lastNode);
}
return lastNode;
}
}
2.
public class Solution {
TreeNode head = null;
TreeNode realHead = null;
public TreeNode Convert(TreeNode pRootOfTree) {
ConvertSub(pRootOfTree);
return realHead;
}
private void ConvertSub(TreeNode pRootOfTree) {
if(pRootOfTree==null) return;
ConvertSub(pRootOfTree.left);//一直遍历左子树,直到为空
//判断左子树空时,链表是否已加入头节点
if (head == null) {//未加入则加入
head = pRootOfTree;
realHead = pRootOfTree;
} else {//已加入,则令当前节点连接到末尾节点之后
head.right = pRootOfTree;
pRootOfTree.left = head;
head = pRootOfTree;
}
ConvertSub(pRootOfTree.right);//遍历右子树
}
}
28.字符串的排列
输入一个字符串,按字典序打印出该字符串中字符的所有排列。例如输入字符串abc,则打印出由字符a,b,c所能排列出来的所有字符串abc,acb,bac,bca,cab和cba。
输入描述:输入一个字符串,长度不超过9(可能有字符重复),字符只包括大小写字母。
将字符串看成两部分,第一个字符和后面的字符。然后求后面字符的排列。对后面的字符也是分两部分。递归。
递归:
对于无重复值的情况:
固定第一个字符,递归取得首位后面的各种字符串组合;
再把第一个字符与后面每一个字符交换,并同样递归获得首位后面的字符串组合; 递归的出口,就是只剩一个字符的时候,递归的循环过程,就是从每个子串的第二个字符开始依次与第一个字符交换,然后继续处理子串。
假如有重复值呢?
由于全排列就是从第一个数字起,每个数分别与它后面的数字交换,我们先尝试加个这样的判断——如果一个数与后面的数字相同那么这两个数就不交换了。
例如abb,第一个数与后面两个数交换得bab,bba。此时有abb,bab,bba;
- 然后abb中第二个数和第三个数相同,就不用交换了。
- 但是对bab,第二个数和第三个数不 同,则需要交换,得到bba。
- 由于这里的bba和开始第一个数与第三个数交换的结果相同了,因此这个方法不行。
换种思维,对abb,第一个数a与第二个数b交换得到bab,然后考虑第一个数与第三个数交换,此时由于第三个数等于第二个数,
所以第一个数就不再用与第三个数交换了。再考虑bab,它的第二个数与第三个数交换可以解决bba。此时全排列生成完毕!
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
public class Solution {
public ArrayList<String> Permutation(String str) {
ArrayList<String> list = new ArrayList<String>();
if(str!=null && str.length()>0){//输入合法的条件下
PermutationHelper(str.toCharArray(),0,list);
Collections.sort(list);
}
return list;
}
private void PermutationHelper(char[] chars,int i,ArrayList<String> list){
if(i == chars.length-1){//如果该轮中首节点已和末尾节点进行了交换
list.add(String.valueOf(chars));//将当前的字符合并为字符串,加入到列表中
}else{
Set<Character> charSet = new HashSet<Character>();//HashSet
for(int j=i;j<chars.length;++j){//将当前序列的首节点依次与后面元素交换
if(j==i || !charSet.contains(chars[j])){
charSet.add(chars[j]);//把当前元素加到HashSet中
swap(chars,i,j);//交换首节点与当前节点
PermutationHelper(chars,i+1,list);//对当前序列中首节点之后的序列进行递归
swap(chars,j,i);//再将首节点交换回原位置,然后与下一个节点进行交换
}
}
}
}
private void swap(char[] cs,int i,int j){
char temp = cs[i];
cs[i] = cs[j];
cs[j] = temp;
}
}
29.数组中出现次数超过一半的数字
数组中有一个数字出现的次数超过数组长度的一半,请找出这个数字。例如输入一个长度为9的数组{1,2,3,2,2,2,5,4,2}。由于数字2在数组中出现了5次,超过数组长度的一半,因此输出2。如果不存在则输出0。
暴力解:利用hashmap存储值和出现次数;
根据数组特点:设元素出现次数,遍历后次数大于0的值是需要判断的值(它出现的次数大于等于其他值)
1.暴力解:
import java.util.Map;
import java.util.HashMap;
public class Solution {
public int MoreThanHalfNum_Solution(int [] array) {
if(array.length==0)
return 0;
//只有一个数字次数超过长度一半?
int limit=array.length/2;
//暴力解
Map<Integer,Integer> result=new HashMap<>();
for(int i=0;i<array.length;++i){
if(!result.containsKey(array[i])){//若不含,是第一次出现,加入map
result.put(array[i],1);
}else{//若已包含,则将计数加1
int count=result.get(array[i]);
result.put(array[i],count+1);
}
}
//遍历
for(Map.Entry<Integer,Integer> entry:result.entrySet()){
if(entry.getValue()>limit)
return entry.getKey();
}
return 0;
}
}
2.根据数据特点:
public class Solution {
public int MoreThanHalfNum_Solution(int [] array) {
if(array.length==0)
return 0;
//只有一个数字次数超过长度一半?
int limit=array.length/2;
//根据数组特点
int temp=array[0];
int count=1;//出现次数
for(int i=1;i<array.length;++i){
if(temp==array[i]){//若相等,则出现次数加1
count++;
}else{//若不等则出现次数-1
count--;
}
if(count==0){//若出现次数为0;则替换为当前数字开始计数
temp=array[i];
count=1;
}
}
//检验是否大于长度一半
count=0;
for(int i=0;i<array.length;i++){
if(array[i]==temp)
count++;
}
if(count> array.length/2)
return temp;
return 0;
}
}
3.先排序然后遍历:
先排序,然后遍历。判断重复数字在遍历结束或下一数字不等时的出现次数是否达到要求。但是排序会丢失原数组的顺序。
快速排序参考:30.最小的k个数
30.最小的k个数
输入n个整数,找出其中最小的K个数。例如输入4,5,1,6,2,7,3,8这8个数字,则最小的4个数字是1,2,3,4,。
暴力解。排序后按序输出。
快排:
import java.util.ArrayList;
public class Solution {
public ArrayList<Integer> GetLeastNumbers_Solution(int [] input, int k) {
//暴力解:遍历k次 时间:O(kn) 空间O(1)
//快速排序得到有序数组,会改动数组
ArrayList<Integer> result=new ArrayList<Integer>();
if(k<=0 || input.length<=0 || k>input.length)
return result;
quickSort(input,0,input.length-1);//快排
for(int i=0;i<k;++i){
result.add(input[i]);
}
return result;
}
public void quickSort(int a[],int low,int high){
if(low<high){
int index=partition(a,low,high);
quickSort(a,low,index-1);
quickSort(a,index+1,high);
}
}
public int partition(int a[],int low,int high){
int mid=a[low];
while(low<high){
while(low<high && a[high]>= mid )
high--;
a[low]=a[high];
while(low<high && a[low]<=mid)
low++;
a[high]=a[low];
}
a[low]=mid;
return low;
}
}
如有错误,欢迎指正