剑指Offer编程整理(四)

本文精选了11道经典的编程题目,覆盖字符串操作、数组处理、链表和二叉树等核心数据结构,提供了多种解题思路及代码实现,旨在帮助读者深入理解编程技巧。

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

1、第一个只出现一次的字符

2、数组中的逆序对

3、两个链表的第一个公共结点

4、数字在排序数组中出现的次数

5、二叉树的深度

6、平衡二叉树

7、数组中只出现一次的数字

8、和为S的连续正数序列

9、和为S的两个数字

10、左旋转字符串

11、翻转单词顺序列


1、第一个只出现一次的字符

(1)问题描述:

在一个字符串(1<=字符串长度<=10000,全部由字母组成)中找到第一个只出现一次的字符,并返回它的位置。

(2)解题思路:

空间换时间

(3)代码实现:

法一:

public static int FirstNotRepeatingChar(String str) {
        if (str == null || str.equals("")){
            return -1;
        }
        char[] chars = str.toCharArray();
        HashMap<Character,Integer> map = new HashMap<Character, Integer>();
        for (char i : chars){
            if (map.get(i) == null){
                map.put(i,1);
            }else {
                map.put(i,map.get(i)+1);
            }
        }

        for (int i=0;i<chars.length;i++){
            if (map.get(chars[i])==1)
                return i;
        }

        return -1;

    }


法二:

public static char FirstNotRepeatingChar(string str)
    {
        if(string.IsNullOrEmpty(str))
        {
            return '\0';
        }

        char[] array = str.ToCharArray();
        const int size = 256;
        // 借助数组来模拟哈希表,只用1K的空间消耗
        uint[] hastTable = new uint[size];
        // 初始化数组
        for (int i = 0; i < size; i++)
        {
            hastTable[i] = 0;
        }

        for (int i = 0; i < array.Length; i++)
        {
            hastTable[array[i]]++;
        }

        for (int i = 0; i < array.Length; i++)
        {
            if (hastTable[array[i]] == 1)
            {
                return array[i];
            }
        }

        return '\0';
    }
法三:

public static int FirstNotRepeatingChar(String str) {
        char[] cs = str.toCharArray();
        List<Character> list = new ArrayList<>();
        List<Character> list2 = new ArrayList<>();
        if (cs.length == 0) {
            return -1;
        }
        for (int i = 0; i < cs.length; i++) {
            if (!list.contains(cs[i])) {
                list.add(cs[i]);
            } else if (list.contains(cs[i])) {
                list2.add(cs[i]);
            }
        }
 
        list.removeAll(list2);
 
        return str.indexOf(list.get(0));
    }
}

2、数组中的逆序对

(1)问题描述:

在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对。输入一个数组,求出这个数组中的逆序对的总数P。并将P对1000000007取模的结果输出。 即输出P%1000000007

(2)解题思路:

法一:常规是冒泡思想

法二:归并:先把数组分隔成子数组, 先统计出子数组内部的逆序对的数目,然后再统计出两个相邻子数组之间的逆序对的数目。在统计逆序对的过程中,还需要对数组进行排序。

(3)代码实现:

public class Solution {
    public int InversePairs(int [] array) {
        if(array.length == 0 || array == null)
            return 0;
        int count = InversePairsCore(array,0,array.length-1);
        return count;
    }
    //用归并排序思想
    private int InversePairsCore(int [] array,int low, int high){
        if(low < high){
            int mid = (low+high)/2;
            int leftCount = InversePairsCore(array,low, mid)%1000000007;
            int rightCount = InversePairsCore(array,mid+1,high)%1000000007;
            int count = 0;//计算数目
            int i = mid;//左边部分
            int j = high;//右边部分
            int k = high-low;//辅助数组
            int[] temp = new int[high-low+1];
            //左右两部分都是从后往前计算
            while(i>=low && j>mid){
                if(array[i] > array[j]){
                    count += j-mid;
                    temp[k--] = array[i--];
                    if(count >= 1000000007)
                        count %= 1000000007;
                }else{
                    temp[k--] = array[j--];
                }
            }
            //添加剩下的前半部分到temp中
            for(;i>=low;i--)
                temp[k--] = array[i];
            //添加剩下的后半部分到temp中
            for(;j>mid;j--)
                temp[k--] = array[j];
            //将排好序的temp复制到array中
            for(int v = 0; v < (high-low+1); v++)
                array[low+v] = temp[v];
            return (leftCount+rightCount+count)%1000000007;
        }
        return 0;
    }
}
3、两个链表的第一个公共结点

(1)问题描述:

输入两个链表,找出它们的第一个公共结点。

(2)解题思路:

法一:首先遍历两个链表得到它们的长度,长的链表比短的链表多若干个结点。在第二次遍历的时候,在较长的链表上先走若干步,接着再同时在两个链表上遍历,找到的第一个相同的结点就是它们的第一个公共结点

法二:将第一个链表的节点存到hashmap中,然后第二个节点依次取出节点在hashmap中查找公共节点。

(3)代码实现:
法一:

public static Node FindFirstCommonNode(Node head1, Node head2)
    {
        // 得到两个链表的长度
        int length1 = GetListLength(head1);
        int length2 = GetListLength(head2);
        int diff = length1 - length2;

        Node headLong = head1;
        Node headShort = head2;
        if (diff < 0)
        {
            headLong = head2;
            headShort = head1;
            diff = length2 - length1;
        }
        // 先在长链表上走几步
        for (int i = 0; i < diff; i++)
        {
            headLong = headLong.nextNode;
        }
        // 再同时在两个链表上遍历
        while (headLong != null && headShort != null && headLong != headShort)
        {
            headLong = headLong.nextNode;
            headShort = headShort.nextNode;
        }

        Node commonNode = headLong;
        return commonNode;
    }

    private static int GetListLength(Node head)
    {
        int length = 0;
        Node tempNode = head;
        while (tempNode != null)
        {
            tempNode = tempNode.nextNode;
            length++;
        }

        return length;
    }
法二:

public ListNode FindFirstCommonNode(ListNode pHead1,ListNode pHead2){
        ListNode list1 = pHead1;
        ListNode list2 = pHead2;

        HashMap<ListNode,Integer> map = new HashMap<ListNode,Integer>();
        while (list1 != null){
            map.put(list1,null);
            list1 = list1.next;
        }

        while (list2 != null){
            if (map.containsKey(list2)){
                return list2;
            }
            list2 = list2.next;
        }

        return null;

    }

4、数字在排序数组中出现的次数

(1)问题描述:

统计一个数字在排序数组中出现的次数。

(2)解题思路:

法一:分情况遍历数组查找;

法二:使用二分法的思想,找出这个数第一次出现的位置和最后一次出现的位置,然后计算出次数。

(3)代码实现:
法一:

public class Solution {
    public int GetNumberOfK(int [] array , int k) {
        int count=0;
        if(array.length==0)
            return 0;
        if(array[0]==k){
            for(int i=0;i<array.length;i++){
                if(array[i]==k)
                    count++;
                else
                    break;
            }
        }else if(array[0]<k){
            for(int i=0;i<array.length;i++){
                if(array[i]==k)
                    count++;
                if(array[i]>k)
                    break;
            }
        }else{
            for(int i=0;i<array.length;i++){
                if(array[i]==k)
                    count++;
                if(array[i]<k)
                    break;
            }
        }
        return count;
    }
}


法二:

public class Solution {
    public int GetNumberOfK(int [] array , int k) {
        int length = array.length;
        if(length == 0){
            return 0;
        }
        int firstK = getFirstK(array, k, 0, length-1);
        int lastK = getLastK(array, k, 0, length-1);
        if(firstK != -1 && lastK != -1){
             return lastK - firstK + 1;
        }
        return 0;
    }
    //递归写法
    private int getFirstK(int [] array , int k, int start, int end){
        if(start > end){
            return -1;
        }
        int mid = (start + end) >> 1;
        if(array[mid] > k){
            return getFirstK(array, k, start, mid-1);
        }else if (array[mid] < k){
            return getFirstK(array, k, mid+1, end);
        }else if(mid-1 >=0 && array[mid-1] == k){
            return getFirstK(array, k, start, mid-1);
        }else{
            return mid;
        }
    }
    //循环写法
    private int getLastK(int [] array , int k, int start, int end){
        int length = array.length;
        int mid = (start + end) >> 1;
        while(start <= end){
            if(array[mid] > k){
                end = mid-1;
            }else if(array[mid] < k){
                start = mid+1;
            }else if(mid+1 < length && array[mid+1] == k){
                start = mid+1;
            }else{
                return mid;
            }
            mid = (start + end) >> 1;
        }
        return -1;
    }
}
5、二叉树的深度

(1)问题描述:

输入一棵二叉树,求该树的深度。从根结点到叶结点依次经过的结点(含根、叶结点)形成树的一条路径,最长路径的长度为树的深度。

(2)解题思路:

递归取左子树与右子树中深度较大的。

(3)代码实现:

public class Solution {
    public int TreeDepth(TreeNode root) {
        if(root==null)
            return 0;
    
        int left=TreeDepth(root.left);
        int right=TreeDepth(root.right);
        
        return Math.max(left,right)+1;
    }
}
6、平衡二叉树

(1)问题描述:

输入一棵二叉树,判断该二叉树是否是平衡二叉树。

(2)解题思路:

平衡二叉树是一棵空树或它的左右两个子树的高度差的绝对值不超过1,且左右两个子树都是平衡二叉树。

(3)代码实现:

public class BalencedTree {

    DepthOfTree depth = new DepthOfTree();
    public boolean isBalenceTree(TreeNode root){
        if (root == null){
            return true;
        }

        int leftCount = depth.TreeDepth(root.left);
        int rightCount = depth.TreeDepth(root.right);
        if (Math.abs(leftCount - rightCount) > 1){
            return false;
        }
        return isBalenceTree(root.left) && isBalenceTree(root.right);

    }
}
7、数组中只出现一次的数字

(1)问题描述:

一个整型数组里除了两个数字之外,其他的数字都出现了两次。请写程序找出这两个只出现一次的数字。

(2)解题思路:

法一:用list来实现。

法二:任何一个数字异或它自己都等于0。

假如这两个数为a和b,那么将所有的数异或得到的数必定为a^b。由于a和b不相等,那么a^b != 0,也就是说在a^b中必定至少有一位为1,对应位上a与b不一样,根据这一位,我们可以将a和b分开,并将数分成两组。注意,两个相同的数肯定会被分到同一个组。 我们在结果数字中找到第一个为1的位的位置,记为第N位。现在我们以第N位是不是1为标准把原数组中的数字分成两个子数组,第一个子数组中每个数字的第N位都为1,而第二个子数组的每个数字的第N位都为0现在我们已经把原数组分成了两个子数组,每个子数组都包含一个只出现一次的数字,而其他数字都出现了两次。

(3)代码实现:

法一:

public void FindNumsAppearOnce(int [] array,int num1[] , int num2[]) {
        List<Integer> list=new ArrayList<Integer>();
        list.add(array[0]);
        for(int i=1;i<array.length;i++){
            if(list.contains(array[i])){
                int index = list.indexOf(array[i]); 
                list.remove(index);
            }else
                list.add(array[i]);
               
        }
        num1[0]=list.get(0);
        num2[0]=list.get(1);
    }

法二:

public int[]findTwoDiff(int[]a)
	{
		int []result=new int[2];
		int temp=0;
		int flag=1;
		for(int t:a)
		{
			temp^=t;
		}
		
		while((temp&flag)==0)
		{
			flag<<=1;
		}
		for(int t:a)
		{
			if((t&flag)==1)
			{
				result[0]^=t;
			}else
			{
				result[1]^=t;
			}
		}
		
		return result;
		
	}
8、和为S的连续正数序列

(1)问题描述:

小明很喜欢数学,有一天他在做数学作业时,要求计算出9~16的和,他马上就写出了正确答案是100。但是他并不满足于此,他在想究竟有多少种连续的正数序列的和为100(至少包括两个数)。没多久,他就得到另一组连续正数和为100的序列:18,19,20,21,22。现在把问题交给你,你能不能也很快的找出所有和为S的连续正数序列? Good Luck!输出所有和为S的连续正数序列,序列内按从小到大的顺序,序列间按开始数字从小到大的顺序。

(2)解题思路:

用两个数字begin和end分别表示序列的最大值和最小值,首先将small初始化为1,big初始化为2。
如果从small到big的和大于s,我们就从序列中去掉较小的值(即增大small),相反,只需要增大big。 
终止条件为:一直增加small到(1+sum)/2并且big小于sum。

(3)实现代码:

public class Solution {
    public ArrayList<ArrayList<Integer>> FindContinuousSequence(int sum) {
        ArrayList<ArrayList<Integer>> lists=new ArrayList<ArrayList<Integer>>();
        if(sum<=1){return lists;}
        int small=1;
        int big=2;
        while(small!=(1+sum)/2){          //当small==(1+sum)/2的时候停止
            int curSum=sumOfList(small,big);
            if(curSum==sum){
                ArrayList<Integer> list=new ArrayList<Integer>();
                for(int i=small;i<=big;i++){
                    list.add(i);
                }
                lists.add(list);
                small++;big++;
            }else if(curSum<sum){
                big++;
            }else{
                small++;
            }
        }
        return lists;
    }
     
    public int sumOfList(int head,int leap){        //计算当前序列的和
        int sum=head;
        for(int i=head+1;i<=leap;i++){
            sum+=i;
        }
        return sum;
    }
}
9、和为S的两个数字

(1)问题描述:

输入一个递增排序的数组和一个数字S,在数组中查找两个数,是的他们的和正好是S,如果有多对数字的和等于S,输出两个数的乘积最小的。

(2)解题思路:

取递增序列中第一个数start和最后一个数end;如果start与end的和小于sum,start加一;如果start与end的和大于sum,end减一;如果start与end的和与sum相同,计算乘积; 如果乘积小于上一个相加等于sum的序列,则清除原序列,添加新序列,否则不变;循环结束标志:start>end; 输出记录的序列。

(3)代码实现:

public ArrayList<Integer> findNumbersWithSum(int [] array, int sum) {
        ArrayList<Integer> list = new ArrayList<Integer>();
        if (array.length == 0){
            return list;
        }
        int start=0, end=array.length-1;
        int temp = Integer.MAX_VALUE;

        while (start < end){
            if (array[start] + array[end] == sum){
                if (temp > array[start]*array[end]){
                    list.clear();
                    list.add(array[start]);
                    list.add(array[end]);
                    temp = array[start]*array[end];
                }
                start++;
                end--;
            }else if (array[start] + array[end]  < sum){
                start++;
            }else {
                end--;
            }

        }
        return list;
    }
10、左旋转字符串

(1)问题描述:

汇编语言中有一种移位指令叫做循环左移(ROL),现在有个简单的任务,就是用字符串模拟这个指令的运算结果。对于一个给定的字符序列S,请你把其循环左移K位后的序列输出。例如,字符序列S=”abcXYZdef”,要求输出循环左移3位后的结果,即“XYZdefabc”。是不是很简单?OK,搞定它!

(2)解题思路:

法一: 通过java实现字符串切割和拼接,这种做法可以,但是比较low;

法二: 举例:abcXYZdef,先将字符串分割成两部分,abc和XYZdef. 分别将abc和XYZdef反转得到字符串"cbafedZYX",再将所得到的字符串整体反转得到XYZdefabc。

(3)代码实现:

法一:

public String LeftRotateString(String str,int n) {
        if(str==null||n>str.length())
            return "";
        StringBuilder s=new StringBuilder(str);
        String s1=s.substring(0,n);
        s.delete(0,n);
        s.append(s1);
        return s.toString();
    }

法二:

public String leftRoate(String str ,int n){
        if (str.length()<=0){
            return "";
        }

        char[] chars = str.toCharArray();
        reverseString(chars,0,n-1);
        reverseString(chars,n,chars.length-1);
        reverseString(chars,0,chars.length-1);
        return String.valueOf(chars);
    }

    public void reverseString(char[] str,int from,int to){
        while (from <= to){
            char t = str[from];
            str[from] = str[to];
            str[to] = t;
            from++;
            to--;
        }
    }
11、翻转单词顺序列

(1)问题描述:

牛客最近来了一个新员工Fish,每天早晨总是会拿着一本英文杂志,写些句子在本子上。同事Cat对Fish写的内容颇感兴趣,有一天他向Fish借来翻看,但却读不懂它的意思。例如,“student. a am I”。后来才意识到,这家伙原来把句子单词的顺序翻转了,正确的句子应该是“I am a student.”。Cat对一一的翻转这些单词顺序可不在行,你能帮助他么?

(2)解题思路:

法一:逆序输出字符串数组;

法二:首先反转整个句子;然后按照空格,反转单词。

(3)代码实现:
法一:

public String ReverseSentence(String str) {
        if(str == null){ return null;}
         if(str.trim().equals("")){
            return str;
        }
        String string = str;
        String[] strings = string.split(" ");
        StringBuilder sBuilder = new StringBuilder();
        for (int i = strings.length-1 ; i>=0;i--) {
            if(i == 0){
                sBuilder.append(strings[i]);
            }else {
                sBuilder.append(strings[i]);
                sBuilder.append(" ");
            }  
        }
     
        String string2 = sBuilder.toString();
        return string2;
    }
}
法二:

public class SentenceReverse {
    LeftRotate leftRotate = new LeftRotate();

    public String ReverseSentence(String str) {

        char[] chars = str.toCharArray();
        leftRotate.reverseString(chars,0,chars.length-1);
        System.out.println(String.valueOf(chars));
        int last = 0;
        for (int i=0;i<chars.length;i++){
            if (chars[i]==' ') {
                leftRotate.reverseString(chars,last,i-1);
                last = i+1;
            }
            if (i == chars.length-1){
                leftRotate.reverseString(chars,last,i);
            }
        }

        return String.valueOf(chars);
    }



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值