剑指Offer学习笔记(刷题ing) Java版

本文深入解析了多个经典面试题目,包括单例模式实现、查找字符串中首个唯一字符、链表及二叉树相关操作、约瑟夫环问题、二叉搜索树转换、复杂链表复制等,提供高效算法及代码示例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1.面试题2 实现单例模式 --- 静态内部类的应用。
        好处: 1.解决多线程安全问题 2.按需创建实例对象,提高内存使用效率。

public class Singleton {
	private Singleton() {}

	public static Singleton getInstance() {
		return Nested.singleton;
	}

	private static class Nested {
		public static final Singleton singleton = new Singleton();
	}
}

2. 面试题50 第一个只出现一次的字符:
        在一个字符串(0<=字符串长度<=10000,全部由字母组成)中找到第一个只出现一次的字符,并返回它的位置, 如果没有则返回 -1(需要区分大小写). (点击前往牛客网)

/**
* 解法一:不使用Map   
* 时间复杂度: O(n)
* 空间复杂度: O(1)
*/
public class Solution {
	public int FirstNotRepeatingChar(String str) {
		if (str.length() == 0 || str == null) return -1;
		int[] hash = new int[56];
		char ans = 0;
		for (int i = 0; i < str.length(); i++) {
			int index = str.charAt(i);
			if (Character.isUpperCase(str.charAt(i))) {
				index -= 65;
			} else {
				index -= 71;
			}
			hash[index]++;
		}
		int pos = 0;
		for (int i = 0; i < str.length(); i++) {
			int index = str.charAt(i);
			if (Character.isUpperCase(str.charAt(i))) {
				index -= 65;
			} else {
				index -= 71;
			}
			if (hash[index] == 1) {
				pos = i;
				break;
			}
		}
		return pos;
	}
}
/**
* 解法二:使用HashMap   
* 时间复杂度: O(n)
* 空间复杂度: O(1)
*/
import java.util.HashMap;
import java.util.Map;
public class Solution {
    public int FirstNotRepeatingChar(String str) {
        if (str == null || str.length() == 0) return -1;
        Map<Character, Integer> map = new HashMap<Character, Integer>();
		for (int i = 0; i < str.length(); i++) {
			if (map.get(str.charAt(i)) == null) {
				map.put(str.charAt(i), 1);
			} else {
				map.put(str.charAt(i), map.get(str.charAt(i)) + 1);
			}
		}
		for (int i = 0; i < str.length(); i++) {
			if (map.get(str.charAt(i)) == 1) {
				return i;
			}
		}
		return 0;
    }
}

3.字符流中第一个不重复的字符:请实现一个函数用来找出字符流中第一个只出现一次的字符。例如,当从字符流中只读出前两个字符"go"时,第一个只出现一次的字符是"g"。当从该字符流中读出前六个字符“google"时,第一个只出现一次的字符是"l"。 (点击前往牛客网)

import java.util.HashMap;
import java.util.Map;

/**
 *和上面的題目类似
 */
public class Solution {
	// Insert one char from stringstream
	Map<Character, Integer> map = new HashMap<Character, Integer>();
	StringBuffer sb = new StringBuffer();

	public void Insert(char ch) {
		sb.append(ch);
		if (map.get(ch) == null) {
			map.put(ch, 1);
		} else {
			map.put(ch, map.get(ch) + 1);
		}
	}

	// return the first appearence once char in current stringstream
	public char FirstAppearingOnce() {
		String str = sb.toString();
		for (int i = 0; i < str.length(); i++) {
			if (map.get(str.charAt(i)) == 1) {
				return str.charAt(i);
			}
		}
		return '#';
	}
}

4.面试题3 数组中重复的数字:在一个长度为n的数组里的所有数字都在0到n-1的范围内。 数组中某些数字是重复的,但不知道有几个数字是重复的。也不知道每个数字重复几次。请找出数组中任意一个重复的数字。 例如,如果输入长度为7的数组{2,3,1,0,2,5,3},那么对应的输出是第一个重复的数字2。 (点击前往牛客网)

public class Solution {
	// Parameters:
	// numbers: an array of integers
	// length: the length of array numbers
	// duplication: (Output) the duplicated number in the array number,length of
	// duplication array is 1,so using duplication[0] = ? in implementation;
	// Here duplication like pointor in C/C++, duplication[0] equal *duplication
	// in C/C++
	// 这里要特别注意~返回任意重复的一个,赋值duplication[0]
	// Return value: true if the input is valid, and there are some duplications
	// in the array number
	// otherwise false
	/**
	 * 时间复杂度:O(n) 空间复杂度:O(1)
	 */
	public boolean duplicate(int numbers[], int length, int[] duplication) {
		if (numbers == null || length == 0) {
			return false;
		}
		for (int i = 0; i < length; i++) {
			if (numbers[i] < 0 || numbers[i] > length - 1)
				return false;
		}
		for (int i = 0; i < length; i++) {
			while (numbers[i] != i) {
				if (numbers[numbers[i]] != numbers[i]) {
					int temp = numbers[numbers[i]];
					numbers[numbers[i]] = numbers[i];
					numbers[i] = temp;
				} else {
					duplication[0] = numbers[i];
					return true;
				}
			}
		}
		return false;
	}
}

5.面试题4 二维数组中的查找:在一个二维数组中(每个一维数组的长度相同),每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。请完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。(点击前往牛客网)

public class Solution {
	/**
	 * 从右上角找起,如果右上角的数大于要查找的数,则去掉这一列;
	 * 如果右上角的数小于要查找的数,则去掉这一行。 
	 * 不使用递归解法
	 */
	public boolean Find(int target, int[][] array) {
		if (array == null) return false;

		if (array.length > 0 && array[0].length > 0) {
			int row = 0;
			int col = array[0].length - 1;
			while (array[row][col] != target) {
				if (array[row][col] > target) {
					col--;
				} else {
					row++;
				}
				if (col < 0 || row > array.length - 1)
					return false;
			}
		} else {
			return false;
		}
		return true;
	}
}
public class Solution {
	/**
	 * 从右上角找起,如果右上角的数大于要查找的数,则去掉这一列; 
	 * 如果右上角的数小于要查找的数,则去掉这一行。
	 * 使用递归解法
	 */
	public boolean Find(int target, int[][] array) {
		if (array == null) return false;
		int row = array.length;
		if (row <= 0) return false;
		int col = array[0].length;
		if (col <= 0) return false;

		if (array[0][col - 1] == target) return true;

		if (array[0][col - 1] > target) {
			int[][] temp = new int[row][col - 1];
			for (int i = 0; i < row; i++) {
				for (int j = 0; j < col - 1; j++) {
					temp[i][j] = array[i][j];
				}
			}
			
			if (Find(target, temp)) return true;
			else return false;
			
		} else {
			int[][] temp = new int[row - 1][col];
			for (int i = 0; i < row - 1; i++) {
				for (int j = 0; j < col; j++) {
					temp[i][j] = array[i + 1][j];
				}
			}
			
			if (Find(target, temp)) return true;
			else return false;
			
		}
	}
}

6.面试题5 替换空格:请实现一个函数,将一个字符串中的每个空格替换成“%20”。例如,当字符串为We Are Happy.则经过替换之后的字符串为We%20Are%20Happy。(点击前往牛客网)

public class Solution {
	/**
	 * 直接利用String封装好的方法
	 */
	public String replaceSpace(StringBuffer str) {
		if (str == null) return null;
		return new String(str).replace(" ", "%20");
	}
}

7.面试题6 从尾到头打印链表:输入一个链表,按链表值从尾到头的顺序返回一个ArrayList。(点击前往牛客网)

/**
*    public class ListNode {
*        int val;
*        ListNode next = null;
*
*        ListNode(int val) {
*            this.val = val;
*        }
*    }
*
*/
import java.util.ArrayList;
import java.util.Stack;
public class Solution {
    public ArrayList<Integer> printListFromTailToHead(ListNode listNode) {
    	Stack<Integer> stack = new Stack<Integer>();
        ArrayList<Integer> array = new ArrayList<Integer>();
        while(listNode != null){
        	stack.push(listNode.val);
        	listNode = listNode.next;
        }
        while(!stack.empty()){
        	array.add(stack.pop());
        }
        return array;
    }
}

8.面试题18 删除链表的节点: 请编写一个函数,使其可以删除某个链表中给定的(非末尾)节点,你将只被给定要求被删除的节点。(点击前往LeetCode)  注:LeetCode此题和剑指Offer上的测试用例略有不同,剑指Offer上测试用例包含了删除的节点是尾节点(只能从前往后遍历,完成删除操作)和链表只有一个节点等特殊情况,需特判。

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
class Solution {
    public void deleteNode(ListNode node) {
        if(node == null)return ;
        node.val = node.next.val;
        node.next = node.next.next;
    }
}

9.面试题22 链表中倒数第k个结点: 输入一个链表,输出该链表中倒数第k个结点。(点击前往牛客网)

/*
public class ListNode {
    int val;
    ListNode next = null;

    ListNode(int val) {
        this.val = val;
    }
}*/
/**   
* 要求只遍历一遍
*/
public class Solution {
    public ListNode FindKthToTail(ListNode head,int k) {
    	if(head == null || k == 0) return null;
    	ListNode p1 = head;
    	ListNode p2 = head;
    	for(int i = 0; i < k - 1; i++){
    		if(p1.next != null){
    			//防止k大于节点数
    			p1 = p1.next;
    		}else{
    			return null;
    		}
    	}
    	while(p1.next != null){
    		p1 = p1.next;
    		p2 = p2.next;
    	}
    	return p2;
    }
}

10.面试题24 反转链表: 输入一个链表,反转链表后,输出新链表的表头。(点击前往牛客网)

/*
public class ListNode {
    int val;
    ListNode next = null;

    ListNode(int val) {
        this.val = val;
    }
}*/
public class Solution {
    public ListNode ReverseList(ListNode head) {
    	if(head == null || head.next == null) return head;
    	ListNode bef = head;
    	ListNode cur = head.next;
    	head.next = null;
    	while(cur != null){
    		ListNode next = cur.next;
    		cur.next = bef;
    		bef = cur;
    		cur = next;
    	}
    	return bef;
    }
}

11.面试题25 合并两个排序的链表: 输入两个单调递增的链表,输出两个链表合成后的链表,当然我们需要合成后的链表满足单调不减规则。(点击前往牛客网)

/*
public class ListNode {
    int val;
    ListNode next = null;

    ListNode(int val) {
        this.val = val;
    }
}*/
public class Solution {
    public ListNode Merge(ListNode list1,ListNode list2) {
    	if(list1 == null) return list2;
    	if(list2 == null) return list1;
        ListNode mergeList = null;
        if(list1.val < list2.val){
        	mergeList = list1;
        	mergeList.next = Merge(list1.next, list2);
        }else {
        	mergeList = list2;
        	mergeList.next = Merge(list1, list2.next);
		}
        return mergeList;
    }
}

12.面试题52 两个链表的第一个公共结点输入两个链表,找出它们的第一个公共结点。(点击前往牛客网)

public class Solution {
    /**
     *不使用栈辅助空间,时间复杂度要求为O(n+m)
     * @return
     */
    public ListNode FindFirstCommonNode(ListNode pHead1, ListNode pHead2) {
    	if(pHead1 == null || pHead2 == null) return null;
    	if(pHead1 == pHead2) return pHead1;
    	int list1Length = 0;
    	ListNode p1 = pHead1;
    	while(p1 != null){
    		list1Length++;
    		p1 = p1.next;
    	}
    	int list2Length = 0;
    	ListNode p2 = pHead2;
    	while(p2 != null){
    		list2Length++;
    		p2 = p2.next;
    	}
    	ListNode ans = null;
    	if(list1Length > list2Length){
    		int more = list1Length - list2Length;
    		for(int i = 0; i < more; i++){
    			pHead1 = pHead1.next;
    		}
    		for(int i = 0; i < list2Length; i++){
    			if(pHead1 == pHead2){
    				ans = pHead1;
    				break;
    			}else {
					pHead1 = pHead1.next;
					pHead2 = pHead2.next;
				}
    		}
    	}
    	if(list1Length < list2Length){
    		int more = list2Length - list1Length;
    		for(int i = 0; i < more; i++){
    			pHead2 = pHead2.next;
    		}
    		for(int i = 0; i < list1Length; i++){
    			if(pHead1 == pHead2){
    				ans = pHead1;
    				break;
    			}else {
					pHead1 = pHead1.next;
					pHead2 = pHead2.next;
				}
    		}
    	}
    	return ans;
    }
}

13.面试题62 孩子们的游戏(圆圈中最后剩下的数):每年六一儿童节,牛客都会准备一些小礼物去看望孤儿院的小朋友,今年亦是如此。HF作为牛客的资深元老,自然也准备了一些小游戏。其中,有个游戏是这样的:首先,让小朋友们围成一个大圈。然后,他随机指定一个数m,让编号为0的小朋友开始报数。每次喊到m-1的那个小朋友要出列唱首歌,然后可以在礼品箱中任意的挑选礼物,并且不再回到圈中,从他的下一个小朋友开始,继续0...m-1报数....这样下去....直到剩下最后一个小朋友,可以不用表演,并且拿到牛客名贵的“名侦探柯南”典藏版(名额有限哦!!^_^)。请你试着想下,哪个小朋友会得到这份礼品呢?(注:小朋友的编号是从0到n-1)(点击前往牛客网)

public class Solution {
    /**
     * 经典的约瑟夫环问题,解决这个问题可以采用模拟或利用递归找出答案。
     * 但是模拟的算法效率较低,所用采用简单的递归找出答案。
     * 此处递归的关键是利用这轮幸运者的下标推出上轮幸运者的下标:
     * f(n,k) = (f(n-1,k) + k) % m
     */
    public int LastRemaining_Solution(int n, int m) {
        if(n < 1 || m < 1)return -1;
        if(n == 1) 
        	return 0;
        else 
        	return (LastRemaining_Solution(n-1, m) + m) % n;
    }
}

关于递归的图解推理,我推荐看这篇文章 约瑟夫环 数学解法 f(n,k)=(f(n-1,k)+k)%n 公式讲解 

14.面试题36 二叉搜索树与双向链表:输入一棵二叉搜索树,将该二叉搜索树转换成一个排序的双向链表。要求不能创建任何新的结点,只能调整树中结点指针的指向。(点击前往牛客网)

public class Solution {
	TreeNode curNode = null; // 当前节点
	TreeNode headNode = null;

	/**
	 * 思路:按中序遍历,遍历完左子树,就将当前节点与根节点串起来。
	 */
	public TreeNode Convert(TreeNode pRootOfTree) {
		if (pRootOfTree == null)
			return null;
		traverse(pRootOfTree);
		return headNode;
	}

	public void traverse(TreeNode root) {
		if (root == null)
			return;
		traverse(root.left);
		if (curNode == null) {
			curNode = root;
			headNode = root;
		} else {
			curNode.right = root;
			root.left = curNode;
			curNode = root;
		}
		traverse(root.right);
	}
}

15.面试题35 复杂链表的复制:输入一个复杂链表(每个节点中有节点值,以及两个指针,一个指向下一个节点,另一个特殊指针指向任意一个节点),返回结果为复制后复杂链表的head。(注意,输出结果中请不要返回参数中的节点引用,否则判题程序会直接返回空)(点击前往牛客网)

public class Solution {
	public RandomListNode Clone(RandomListNode pHead) {
		if (pHead == null) return null;
		
		RandomListNode currentNode = pHead;
		while (currentNode != null) {
			RandomListNode cloneNode = new RandomListNode(currentNode.label);
			RandomListNode nextNode = currentNode.next;
			currentNode.next = cloneNode;
			cloneNode.next = nextNode;
			currentNode = nextNode;
		}
		
		currentNode = pHead;
		while (currentNode != null) {
			
			currentNode.next.random = currentNode.random == null ? null : currentNode.random.next;
			currentNode = currentNode.next.next;
		}
		
		currentNode = pHead;
		RandomListNode copylist = pHead.next;
		while (currentNode != null) {
			RandomListNode cloneNode = currentNode.next;
			currentNode.next = cloneNode.next;
			cloneNode.next = cloneNode.next == null ? null : cloneNode.next.next;
			currentNode = currentNode.next;
		}
		return copylist;
	}
}

16.面试题7 重建二叉树:输入某二叉树的前序遍历和中序遍历的结果,请重建出该二叉树。假设输入的前序遍历和中序遍历的结果中都不含重复的数字。例如输入前序遍历序列{1,2,4,7,3,5,6,8}和中序遍历序列{4,7,2,1,5,3,8,6},则重建二叉树并返回。(点击前往牛客网)

public class Tree1 {
	public TreeNode reConstructBinaryTree(int[] pre, int[] in) {
		if(pre.length == 0 || in.length == 0 || pre == null || in == null) {
			return null;
		}
		return traverseTree(pre, 0, pre.length -1, in, 0, in.length - 1);
	}
	
	public TreeNode traverseTree(int[] pre, int startPre, int endPre, int[] in, int startIn, int endIn) {
		if(startPre > endPre || startIn > endIn) return null;
		TreeNode treeNode = new TreeNode(pre[startPre]);
		for(int i = startIn; i <= endIn; i++) {
			if(in[i]== pre[startPre]) {
				//i - startIn 是在中序数组中根节点左边的左子树节点个数
				treeNode.left = traverseTree(pre, startPre+1, startPre + (i - startIn), in, startIn, i - 1);
				treeNode.right = traverseTree(pre, startPre + (i - startIn) + 1, endPre, in, i+1, endIn);
				break;
			}
		}
		return treeNode;
	}
}

17.面试题8 二叉树的下一个结点:给定一个二叉树和其中的一个结点,请找出中序遍历顺序的下一个结点并且返回。注意,树中的结点不仅包含左右子结点,同时包含指向父结点的指针。(点击前往牛客网)

public class Solution {
    public TreeLinkNode GetNext(TreeLinkNode pNode)
    {
        if(pNode == null) return null;
        if(pNode.right != null) {
        	//如果该节点有右子树
        	TreeLinkNode pr = pNode.right;
        	while (pr.left !=null) {
				pr = pr.left;
			}
        	return pr;
        }else if (pNode.next == null) {
			return null;
		}else if (pNode.right == null && pNode.next.left == pNode) {
        	//如果该节点没有右子树且是一个父节点的左子节点
			return pNode.next;
		}else if (pNode.right == null &&pNode.next.left != pNode) {
			//如果该节点没有右子树且不是一个父节点的左子节点
			TreeLinkNode node = pNode.next;
			while(node != null) {
				if(node.next != null && node.next.left == node) {
					return node.next;
				}
				node = node.next;
			}
			return node;
		}
		return null;
    }
}

18. 面试题39 数组中出现次数超过一半的数字: 数组中有一个数字出现的次数超过数组长度的一半,请找出这个数字。例如输入一个长度为9的数组{1,2,3,2,2,2,5,4,2}。由于数字2在数组中出现了5次,超过数组长度的一半,因此输出2。如果不存在则输出0。(点击前往牛客网)

import java.util.*;
public class Solution {
    public int MoreThanHalfNum_Solution(int [] array) {
        if(array.length == 0 || array == null) return 0;
        int half = array.length / 2;
        Map<Integer, Integer> map = new HashMap<Integer, Integer>();
        for(int i = 0; i < array.length; i++){
            if(map.get(array[i]) == null){
                map.put(array[i], 1);
                if(map.get(array[i]) > half) return array[i];
            }else{
                int count = map.get(array[i]);
                map.put(array[i], count+1);
                if(map.get(array[i]) > half) return array[i];
            }
        }
        return 0;
    }
}

19. 输入一个整数,输出该数二进制表示中1的个数。其中负数用补码表示。(点击前往牛客网)

public class Solution {
    //解法1: n &= (n - 1)操作实质是抹掉最右边的1,因此循环次数只与1的个数有关
    public int NumberOf2(int n) {
        int result = 0;
        while(n != 0){
            result++;
            n = n & (n - 1);
        }
        return result;
    }
    
    //解法2:首先把n和1做与运算,判断n的最低位是不是为1。
    //接着把1左移一位得到2,再和n做与运算,就能判断n的次低位是不是1…….
    //这样反复左移,每次都能判断n的其中一位是不是1。
    public int NumberOf2(int n) {
        int result = 0;
        int base = 1;
        while(base != 0){
            if((n & base) != 0){
                result++;
            }
            base = base << 1;
        }
        return result;
    }

    //解法3: 利用无符号右移, 数n每次进行无符号右移一位,检查最右边的bit是否为1来进行统计
    public int NumberOf3(int n) {
        int result = 0;
          while (n != 0) {
            result += n & 1;
            n >>>= 1;
          }
        return result;
    }
}

20. 给定一个double类型的浮点数base和int类型的整数exponent。求base的exponent次方。保证base和exponent不同时为0

public class Solution {
    public double Power(double base, int exponent) {
        if(base == 0) return 0;
        if(exponent == 0) return 1;
        double result = 1;
        if(exponent > 0){
            //正次幂
            for(int i = 0; i < exponent; i++){
                result *= base;
            }
        }else{
            //-负次幂
            exponent = -exponent;
            for(int i = 0; i < exponent; i++){
                result = result / base;
            }
        }
        return result;
  }
}

21. 输入一个整数数组,实现一个函数来调整该数组中数字的顺序,使得所有的奇数位于数组的前半部分,所有的偶数位于数组的后半部分,并保证奇数和奇数,偶数和偶数之间的相对位置不变。

import java.util.ArrayList;

public class Solution {
    /*
     *1. 如果array数组为null, 或长度为0 return
     *2. 遍历一次数组, 将奇数存到一个数组中, 将偶数存到一个数组中
     *3. 将奇数数组和偶数数组替换掉原来数组的内容
     */
    public void reOrderArray(int [] array) {
        if(array == null || array.length == 0) return ;
        ArrayList<Integer> oddArray = new ArrayList<>();
        ArrayList<Integer> evenArray = new ArrayList<>();
        for(int i = 0; i < array.length; i++){
            if(array[i] % 2 == 1){
                oddArray.add(array[i]);
            }else{
                evenArray.add(array[i]);
            }
        }
        for(int i = 0; i < oddArray.size(); i++){
            array[i] = oddArray.get(i);
        }
        for(int i = oddArray.size(); i < evenArray.size()+oddArray.size(); i++){
            array[i] = evenArray.get(i-oddArray.size());
        }
    }
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值