腾讯50题

本文介绍了五个编程问题,涉及链表操作(如两数相加、反转链表)、数学逻辑(如回文数、最接近三数之和),以及数组和整数操作(如合并排序、买卖股票II)。通过实例演示和高效算法,深入探讨了这些经典问题的解决方案。
摘要由CSDN通过智能技术生成
  1. 两数相加
    给你两个 非空 的链表,表示两个非负的整数。它们每位数字都是按照 逆序 的方式存储的,并且每个节点只能存储 一位 数字。

请你将两个数相加,并以相同形式返回一个表示和的链表。

你可以假设除了数字 0 之外,这两个数都不会以 0 开头。

示例 1:

输入:l1 = [2,4,3], l2 = [5,6,4]
输出:[7,0,8]
解释:342 + 465 = 807.

思路:设置进位,需要注意的是,如果加到最后一位进位不为0,则需要向前进1
sum = l1+l2+carry mod 10
carry = l1 + l2 + carry / 10
头节点用来返回

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
        ListNode head = null;
        ListNode tail = null;
        int carry = 0;
        int sum = 0;
        while(l1 != null || l2 != null){
            int num1 = l1== null? 0:l1.val;
            int num2 = l2== null? 0:l2.val;
            sum = num1+num2+carry;
            if(head == null){
                head =tail= new ListNode(sum%10);
               
            }
            else{
                tail.next = new ListNode(sum%10);
                tail = tail.next;

            }
            carry = sum/10;
            if(l1!=null){
                l1 = l1.next;
            }
            if(l2!=null){
                l2 = l2.next;
            }
            
        }
        if(carry > 0){
            tail.next = new ListNode(1);
        }
        return head;

    }
}
  1. 回文数
    给你一个整数 x ,如果 x 是一个回文整数,返回 true ;否则,返回 false 。

回文数是指正序(从左向右)和倒序(从右向左)读都是一样的整数。

例如,121 是回文,而 123 不是。

示例 1:

输入:x = 121
输出:true

思路:反转整个数字,x%10取出最后一个数,x/10删除最后一个数

class Solution {
    public boolean isPalindrome(int x) {
        int res = 0;
        int backup = x;
        if(x < 0){
            return false;
        }
        while(x > 0){
            int temp = x % 10;
            x = x / 10;
            res = res*10 + temp;
        }
        return backup == res;
    }
}
  1. 盛最多水的容器
    给定一个长度为 n 的整数数组 height 。有 n 条垂线,第 i 条线的两个端点是 (i, 0) 和 (i, height[i]) 。

找出其中的两条线,使得它们与 x 轴共同构成的容器可以容纳最多的水。

返回容器可以储存的最大水量。

说明:你不能倾斜容器。

思路: 双指针,一开始指向一头一尾,计算res,如果左边大,则右值向左移动,反之左值向右

class Solution {
    public int maxArea(int[] height) {
        int start = 0;
        int end = height.length-1;
        int res = 0;
        while(end > start){
            res = Math.max(res,Math.min(height[start],height[end]) * (end-start));
            if(height[start] < height[end]){
                start++;
            }
            else{
                end--;
            }
        }
        return res;
    }
}
  1. 最长公共前缀
    编写一个函数来查找字符串数组中的最长公共前缀。

如果不存在公共前缀,返回空字符串 “”。

示例 1:

输入:strs = [“flower”,“flow”,“flight”]
输出:“fl”

思路:纵向查找,以第一个str作为基准,比较之后的每个对应位置上的char是否一致,如果不一致则直接返回str之前的值

class Solution {
    public String longestCommonPrefix(String[] strs) {
        if(strs.length == 0){
            return "";
        }
        int length = strs[0].length();
        int count = strs.length;
        
        for(int i = 0; i < length; i++){
            char c = strs[0].charAt(i);
            for(int j = 1; j < count; j++){
                if(strs[j].length() == i || strs[j].charAt(i) != c){
                    return strs[0].substring(0,i);
                }
            }
        }
        return strs[0];
    }
}
  1. 三数之和
    给你一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?请你找出所有和为 0 且不重复的三元组。

注意:答案中不可以包含重复的三元组。

示例 1:

输入:nums = [-1,0,1,2,-1,-4]
输出:[[-1,-1,2],[-1,0,1]]

思路:双指针,先对数组进行排序,然后固定一个值,两个指针分别指向固定指针后面数组的头和尾,变成两数之和。需要注意的是,如果发现满足条件的三元组后,不能直接break,应该让左指针右移,右指针左移,接着循环。

class Solution {
    public List<List<Integer>> threeSum(int[] nums) {
        Arrays.sort(nums);
        Set<List<Integer>> res = new HashSet<>();
        for(int i = 0; i < nums.length; i++){
            List<Integer> temp = new ArrayList<>();
            int start = i+1;
            int end = nums.length-1;
            while(start < end){
                if(nums[start] + nums[end] == 0 - nums[i]){
                    temp.add(nums[start]);
                    temp.add(nums[end]);
                    temp.add(nums[i]);
                    res.add(new ArrayList(temp));
                    start++;
                    end--;
                    temp.clear();
                }
                else if(nums[start] + nums[end] > 0 - nums[i]){
                    end--;
                }
                else{
                    start++;
                }
            }
        }
        return new ArrayList(res);
    }
}
  1. 最接近的三数之和
    给你一个长度为 n 的整数数组 nums 和 一个目标值 target。请你从 nums 中选出三个整数,使它们的和与 target 最接近。

返回这三个数的和。

假定每组输入只存在恰好一个解。

示例 1:

输入:nums = [-1,2,1,-4], target = 1
输出:2
解释:与 target 最接近的和是 2 (-1 + 2 + 1 = 2) 。

思路:类似三数之和,只不过每次保存一个best装三数之和,如果sum和target差值小于best和target的,则更新best

class Solution {
    public int threeSumClosest(int[] nums, int target) {
        Arrays.sort(nums);
        int best = Integer.MAX_VALUE;
        for(int i = 0; i < nums.length; i++){
            int start = i+1;
            int end = nums.length-1;
            
            while(start < end){
                int sum = nums[start] + nums[end] +nums[i];
                if(sum == target){
                    
                    return target;
                }
                else if(sum > target){
                    end--;
                }
                else{
                    start++;
                    
                }
                if(Math.abs(sum-target) < Math.abs(best-target)){
                        best = sum;
                }
            
        }
        

    }
   				 return best;
    }
}
  1. 全排列
    给定一个不含重复数字的数组 nums ,返回其 所有可能的全排列 。你可以 按任意顺序 返回答案。

示例 1:

输入:nums = [1,2,3]
输出:[[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]

思路: 递归回溯法,设置一个visited记录当前节点是否已经被访问过
相当于暴力法,只要当前节点没有访问过就放到output中,访问结束之后删除所有痕迹

class Solution {
    public List<List<Integer>> permute(int[] nums) {
        List<List<Integer>> res = new ArrayList<>();
        int[] visited = new int[nums.length];
        List<Integer> output = new ArrayList<>();
        digui(nums,nums.length,0,res,visited,output);
        return res;

    }

    public void digui(int nums[], int n, int first, List<List<Integer>> res,int[] visited, List<Integer> output){
        if(first == n){
            res.add(new ArrayList(output));
            return;
        }
        for(int i = 0; i < n; i++){
           if(visited[i] == 0){
               visited[i] = 1;
               output.add(nums[i]);
               digui(nums,n,first+1,res,visited,output);
               output.remove(output.size()-1);
               visited[i] = 0;
           }
           else{
               continue;
           }
            
            

        }
    }
}
  1. 螺旋矩阵
    给你一个 m 行 n 列的矩阵 matrix ,请按照 顺时针螺旋顺序 ,返回矩阵中的所有元素。
    在这里插入图片描述
    示例 1:
    输入:matrix = [[1,2,3],[4,5,6],[7,8,9]]
    输出:[1,2,3,6,9,8,7,4,5]

思路: 模拟
设置一个方向数组,当到边界或者遇到端点的时候就转方向,直到所有格子都走了了一遍,判定端点可以使用辅助数组visited,表示已经走过的点,如果发现这个点已经走过就立即换方向。

class Solution {
    public List<Integer> spiralOrder(int[][] matrix) {
        List<Integer> res = new ArrayList<>();
        int row = matrix.length;
        int col = matrix[0].length;
        int r = 0;
        int c = 0;
        boolean[][] visited = new boolean[row][col];
        int[][] directions = {{0,1},{1,0},{0,-1},{-1,0}};
        int directionIndex = 0;
        int total = row * col;
        for(int i = 0; i < total; i++){
            res.add(matrix[r][c]);
            visited[r][c] = true;
            int nextRow = r + directions[directionIndex][0];
            int nextCol = c + directions[directionIndex][1];
            if(nextRow == row || nextRow < 0 || nextCol == col || nextCol < 0 || visited[nextRow][nextCol]){
                directionIndex = (directionIndex + 1) % 4;
            }
            r = r + directions[directionIndex][0];
            c = c + directions[directionIndex][1];
        }
        return res;
    }
}
  1. 旋转链表
    给你一个链表的头节点 head ,旋转链表,将链表每个节点向右移动 k 个位置。

示例 1:
在这里插入图片描述

输入:head = [1,2,3,4,5], k = 2
输出:[4,5,1,2,3]

思路: 模拟
找到分叉点,将tail连接上head,然后在分叉点断开,返回分叉点,要注意的是k可能会大于length,所以需要取模

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public ListNode rotateRight(ListNode head, int k) {
        if(head == null){
            return null;
        }
        int length = 0;
        ListNode tail = head;
        while(tail.next != null){
            tail = tail.next;
            length++;
        }
        length++;
        int rotate = k % length;
        tail.next = head;
        for(int i = 0; i < (length-rotate-1); i++){
            head = head.next;
        }
        ListNode res = head.next;
        head.next = null;
        return res;
    }
}
  1. 子集
    给你一个整数数组 nums ,数组中的元素 互不相同 。返回该数组所有可能的子集(幂集)。

解集 不能 包含重复的子集。你可以按 任意顺序 返回解集。

示例 1:

输入:nums = [1,2,3]
输出:[[],[1],[2],[1,2],[3],[1,3],[2,3],[1,2,3]]

思路:递归回溯,参照模板写就行

class Solution {
    List<List<Integer>> res = new ArrayList<>();
    List<Integer> temp = new ArrayList<>();
    public List<List<Integer>> subsets(int[] nums) {
        
        digui(nums,0);
        return res;

    }
    public void digui(int[] nums, int position){
        res.add(new ArrayList(temp));
        if(position >= nums.length){
            return;
        }
        
        for(int i = position; i < nums.length; i++){
            temp.add(nums[i]);
            digui(nums,i+1);
            temp.remove(temp.size()-1);
        }
    }

}
  1. 合并两个有序数组
    给你两个按 非递减顺序 排列的整数数组 nums1 和 nums2,另有两个整数 m 和 n ,分别表示 nums1 和 nums2 中的元素数目。

请你 合并 nums2 到 nums1 中,使合并后的数组同样按 非递减顺序 排列。

注意:最终,合并后数组不应由函数返回,而是存储在数组 nums1 中。为了应对这种情况,nums1 的初始长度为 m + n,其中前 m 个元素表示应合并的元素,后 n 个元素为 0 ,应忽略。nums2 的长度为 n 。

示例 1:

输入:nums1 = [1,2,3,0,0,0], m = 3, nums2 = [2,5,6], n = 3
输出:[1,2,2,3,5,6]
解释:需要合并 [1,2,3] 和 [2,5,6] 。
合并结果是 [1,2,2,3,5,6] ,其中斜体加粗标注的为 nums1 中的元素。
思路:逆向双指针,从尾开始添加元素,要注意边界的判断,如果一边为-1了就选另一边,直到两个指针都为-1

class Solution {
    public void merge(int[] nums1, int m, int[] nums2, int n) {
        int i = m-1;
        int j = n-1;
        int position = nums1.length-1;
        while(i>=0 || j>=0){
            if(i < 0){
                nums1[position] = nums2[j];
                position--;
                j--;
                continue;
            }
            else if(j < 0){
                nums1[position] = nums1[i];
                position--;
                i--;
                continue;
            }

            else if( nums1[i] < nums2[j]){
                nums1[position] = nums2[j];
                position--;
                j--;
            }
            else{
                nums1[position] = nums1[i];
                position--;
                i--;
            }
        }
 
    }
}
  1. 格雷编码
    n 位格雷码序列 是一个由 2n 个整数组成的序列,其中:
    每个整数都在范围 [0, 2n - 1] 内(含 0 和 2n - 1)
    第一个整数是 0
    一个整数在序列中出现 不超过一次
    每对 相邻 整数的二进制表示 恰好一位不同 ,且
    第一个 和 最后一个 整数的二进制表示 恰好一位不同
    给你一个整数 n ,返回任一有效的 n 位格雷码序列 。

示例 1:

输入:n = 2
输出:[0,1,3,2]
解释:
[0,1,3,2] 的二进制表示是 [00,01,11,10] 。

  • 00 和 01 有一位不同
  • 01 和 11 有一位不同
  • 11 和 10 有一位不同
  • 10 和 00 有一位不同
    [0,2,3,1] 也是一个有效的格雷码序列,其二进制表示是 [00,10,11,01] 。
  • 00 和 10 有一位不同
  • 10 和 11 有一位不同
  • 11 和 01 有一位不同
  • 01 和 00 有一位不同

思路:在这里插入图片描述
在这里插入图片描述

class Solution {
    public List<Integer> grayCode(int n) {
        List<Integer> res = new ArrayList<>();
        res.add(0);
        int head = 1;
        for(int i = 0; i < n; i++){
            for(int j = res.size()-1; j >= 0; j--){
                res.add(res.get(j)+head);
            }
            head <<= 1;
        }
        return res;
    }
}


  1. 买卖股票的最佳时机 II
    给你一个整数数组 prices ,其中 prices[i] 表示某支股票第 i 天的价格。

在每一天,你可以决定是否购买和/或出售股票。你在任何时候 最多 只能持有 一股 股票。你也可以先购买,然后在 同一天 出售。

返回 你能获得的 最大 利润 。

示例 1:

输入:prices = [7,1,5,3,6,4]
输出:7
解释:在第 2 天(股票价格 = 1)的时候买入,在第 3 天(股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5 - 1 = 4 。
随后,在第 4 天(股票价格 = 3)的时候买入,在第 5 天(股票价格 = 6)的时候卖出, 这笔交易所能获得利润 = 6 - 3 = 3 。
总利润为 4 + 3 = 7 。

思路: dp[i][j] 代表第i天获取的最大利润,j可选0和1,
dp[0][0] 代表第i天不持股,1代表第i天持股
于是,如果当天不持股,则说明可能是前一天持股卖出,也可能是前一天不持股不卖
如果当天持股,则可能是前一天持股今天没卖,或者前一天不持股今天买入
比较最大值,返回最后一天不持股的最大值

class Solution {
    public int maxProfit(int[] prices) {
        int[][] dp = new int[prices.length][2];
        dp[0][0] = 0;
        dp[0][1] = -prices[0];
        for(int i = 1; i < prices.length; i++){
            dp[i][0] = Math.max(dp[i-1][0],dp[i-1][1]+prices[i]);
            dp[i][1] = Math.max(dp[i-1][1],dp[i-1][0]-prices[i]);
        }
        return dp[prices.length-1][0];
    }
}
  1. 只出现一次的数字
    给定一个非空整数数组,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现了一次的元素。

说明:

你的算法应该具有线性时间复杂度。 你可以不使用额外空间来实现吗?

示例 1:

输入: [2,2,1]
输出: 1

思路: 使用异或,
两个相同的数异或为0,0和任何数异或为原数,
根据异或分配律,所以循环完成后只剩下0和只出现一次的数进行异或,为原数

  1. 反转链表
    给你单链表的头节点 head ,请你反转链表,并返回反转后的链表。

示例 1:

输入:head = [1,2,3,4,5]
输出:[5,4,3,2,1]

思路:存储当前节点cur,前序节点pre,后续节点next,每次先保存next,接着把cur.next设置成pre,pre迁移到cur,cur迁移到next

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public ListNode reverseList(ListNode head) {
        if(head == null){
            return null;
        }
        ListNode p1 = null;
        ListNode p2 = head;

        while(p2 != null){
            ListNode temp = p2.next;
            p2.next = p1;
            p1 = p2;
            p2 = temp;
        }
        return p1;
    }
}
  1. 数组中的第K个最大元素
    给定整数数组 nums 和整数 k,请返回数组中第 k 个最大的元素。

请注意,你需要找的是数组排序后的第 k 个最大的元素,而不是第 k 个不同的元素。

你必须设计并实现时间复杂度为 O(n) 的算法解决此问题。

示例 1:

输入: [3,2,1,5,6,4], k = 2
输出: 5
示例 2:

输入: [3,2,3,1,2,4,5,5,6], k = 4
输出: 4

思路 1: 基于快排
首先使用partition找到末尾数字改在的位置,并且返回该位置,之后判断此位置是否为k,如果比k小,移动right到index-1,反之亦然

class Solution {
    public int findKthLargest(int[] nums, int k) {
        int left = 0;
        int right = nums.length-1;
        while(true){
            int index = partition(nums,left,right);
            if(index == nums.length-k){
                return nums[index];
            }
            else if(index > nums.length-k){
                right = index-1;
            }
            else{
                left = index+1;
            }
        }
        

        
    }
    public void quickSort(int[] arr, int begin, int end) {
	if (begin < end){
	    int p = partition(arr, begin, end);
	    // int p = partition2(arr, begin, end);
	    quickSort(arr, begin, p - 1);
	    quickSort(arr, p + 1, end);
	}
}
    

public  int partition(int[] arr, int start, int end) {
        int point = arr[end];
        int i = start;
        int j = end;
        while (i < j) {
            while(i < j && arr[i] <= point) ++i;//注意这里时小于等于
            while(i < j && arr[j] >= point) --j;
            if(i < j){
                swap(arr,i,j);
            }
        }
        swap(arr,i,end);
        return i;
    }

    public void swap(int[] nums, int i, int j){
        int temp = nums[i];
        nums[i] = nums[j];
        nums[j] = temp;
    }
}

思路二大顶堆:

class Solution {
    public int findKthLargest(int[] nums, int k) {
        buildHeap(nums, nums.length);
        int heapSize = nums.length;
        for (int i = nums.length - 1; i >= nums.length - k + 1; --i) {
            swap(nums, 0, i);
            --heapSize;
            maxHeap(nums, 0, heapSize);
        }

        return nums[0];
    }
    public void buildHeap(int[] nums,int size){
        for(int i = size/2; i >= 0; i--){
            maxHeap(nums,i,size);
        }
    }
    public void maxHeap(int[] nums, int i,int size){
        int largest = i;
        int l = 2*i + 1;
        int r = 2*i + 2;
        if(l < size && nums[l] > nums[largest]){
            
            largest = l;
        }
        if(r < size && nums[r] > nums[largest]){
            
            largest = r;
        }
        if(largest != i){
            swap(nums,i,largest);
            maxHeap(nums,largest,size);
        }
        
    }
    public void swap(int[] nums, int i, int j){
        int temp = nums[i];
        nums[i] = nums[j];
        nums[j] = temp;
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值