每天一道算法题

1.旋转数组

给定一个数组,将数组中的元素向右移动 k 个位置,其中 k 是非负数。

进阶:

尽可能想出更多的解决方案,至少有三种不同的方法可以解决这个问题。
你可以使用空间复杂度为 O(1) 的 原地 算法解决这个问题吗?

示例 1:

输入: nums = [1,2,3,4,5,6,7], k = 3
输出: [5,6,7,1,2,3,4]
解释:
向右旋转 1 步: [7,1,2,3,4,5,6]
向右旋转 2 步: [6,7,1,2,3,4,5]
向右旋转 3 步: [5,6,7,1,2,3,4]

作者:力扣 (LeetCode)
链接:https://leetcode-cn.com/leetbook/read/top-interview-questions-easy/x2skh7/
来源:力扣(LeetCode)

当数值大于数组长度时,k的值必须使用 k = k % length
本次采用的是反转数组,分为三部,前面全部、左边和右边

我写的反装函数是这样的

  int temp = 0;
        int i = start;
        int j = end;
        for( ; i < j ; i++ , j --){
           temp = nums[i] ;
           nums[i] = nums[j];
           nums[j] = temp;
        }

别人写的是这样的


         while (start < end) {
            int temp = nums[start];
            nums[start++] = nums[end];
            nums[end--] = temp;
        }

原理是采用双指针,和i++先赋值后++的原理

总体代码

class Solution {
    public void rotate(int[] nums, int k) {
            int length = nums.length-1;
            k %= length;
            reverse(nums,0,length);
            reverse(nums,0,k-1);
            reverse(nums,k,length);
    }

    public void reverse(int[] nums, int start, int end){
        int temp = 0;
        int i = start;
        int j = end;
        for( ; i < j ; i++ , j --){
           temp = nums[i] ;
           nums[i] = nums[j];
           nums[j] = temp;
        }

         while (start < end) {
            int temp = nums[start];
            nums[start++] = nums[end];
            nums[end--] = temp;
        }


    }
}

另外一种做法是
利用 k = k % length 去获取每个值旋转后的位置,然后利用一个新的数组去存取。

存在重复元素

给定一个整数数组,判断是否存在重复元素。

如果存在一值在数组中出现至少两次,函数返回 true 。如果数组中每个元素都不相同,则返回 false 。

示例 1:

输入: [1,2,3,1]
输出: true
示例 2:

输入: [1,2,3,4]
输出: false
示例 3:

输入: [1,1,1,3,3,4,3,2,4,2]
输出: true

利用两种方式去解决,
第一种是暴力解法,但是会导致算法直接超时间。
所以可以使用先排序,因为排序后数组顺序是有序的。

第二种解法是利用Set解法,采用Set解法之后,因为add方法会对重复元素返回false

import java.util.*;
class Solution {
    public boolean containsDuplicate(int[] nums) {
        Set<Integer> set = new HashSet<Integer>();
        for(int i = 0; i < nums.length ; i++){
            if(!set.add(nums[i])){
                return true;
            }
        }
        
        return false;
    }
}

总结:在思考问题时候可以考虑一些集合框架和数据结构能否对应。

只出现一次的数字

给定一个非空整数数组,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现了一次的元素。

说明:

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

示例 1:

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

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

使用异或 (我也是第一次接触到这样)
1 ^ 0 = 1
0 ^ 0 = 0

而且通过断点后是可以满足交换律的
a ^ b ^ a = a ^ a ^ b

class Solution {
    public int singleNumber(int[] nums) {
        int result = 0 ;
        for(int num : nums){
            result ^= num;
        }
        return result;

    }
}

两个数组的交集 II

两个数组的交集 II
给定两个数组,编写一个函数来计算它们的交集。

示例 1:

输入:nums1 = [1,2,2,1], nums2 = [2,2]
输出:[2,2]
示例 2:

输入:nums1 = [4,9,5], nums2 = [9,4,9,8,4]
输出:[4,9]

说明:

输出结果中每个元素出现的次数,应与元素在两个数组中出现次数的最小值一致。
我们可以不考虑输出结果的顺序。
进阶:

如果给定的数组已经排好序呢?你将如何优化你的算法?
如果 nums1 的大小比 nums2 小很多,哪种方法更优?
如果 nums2 的元素存储在磁盘上,内存是有限的,并且你不能一次加载所有的元素到内存中,你该怎么办?

坏味道
int i,j = 0;是不行的
int i =0 ,j = 0;是可行的

ArrayList 只能存放Integer类型,故其toArray()方式不适合转换为int。最后需要手动转换为数组。

思路:
这道题的思路依然是先排序,发现一个道理,好像数组问题,都是排序后好解决很多。

import java.util.*;
class Solution {
    public int[] intersect(int[] nums1, int[] nums2) {
    
        Arrays.sort(nums1);
        Arrays.sort(nums2);

        List<Integer> result = new ArrayList<>();
        int i = 0,j = 0;
        while(i<nums1.length && j < nums2.length){
            if(nums1[i] == nums2[j]){
                result.add(nums1[i]);
                i++;
                j++;
            }else{
                boolean jude = nums1[i] > nums2[j];
                i = jude ? i : ++i; 
                j = jude ? ++j : j;
            }
        }
        
        int[] resultToArray = new int[result.size()];
        int z = 0;
        for(int nums : result){
            resultToArray[z++] = nums;
        }
        return resultToArray;


    }
}

移动零

给定一个数组 nums,编写一个函数将所有 0 移动到数组的末尾,同时保持非零元素的相对顺序。

示例:

输入: [0,1,0,3,12]
输出: [1,3,12,0,0]
说明:

必须在原数组上操作,不能拷贝额外的数组。
尽量减少操作次数。

作者:力扣 (LeetCode)
链接:https://leetcode-cn.com/leetbook/read/top-interview-questions-easy/x2ba4i/
来源:力扣(LeetCode)

解题思路,利用双指针
自己的方法

class Solution {
    public void moveZeroes(int[] nums) {
        int fast = 1;
        int low = 0;
        while(fast < nums.length){
            if(nums[low] == 0 && nums[low] != nums[fast] ){
                int temp = nums[low];
                nums[low++] = nums[fast];
                nums[fast++] = temp;
            }else if(nums[low] == 0 && nums[low] == nums[fast] ) {
                fast ++ ;
            }else{
                fast ++ ;
                low ++ ;
            }
        }
    }
}

别人的方法,是采用不为零的往前移动。

 public void moveZeroes(int[] nums) {
        if (nums == null || nums.length == 0)
            return;
        int index = 0;
        //一次遍历,把非零的都往前挪
        for (int i = 0; i < nums.length; i++) {
            if (nums[i] != 0)
                nums[index++] = nums[i];
        }
        //后面的都是0,
        while (index < nums.length) {
            nums[index++] = 0;
        }
    }

作者:数据结构和算法
链接:https://leetcode-cn.com/leetbook/read/top-interview-questions-easy/x2ba4i/?discussion=AJ2rEF
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

有效的数独

请你判断一个 9x9 的数独是否有效。只需要 根据以下规则 ,验证已经填入的数字是否有效即可。

数字 1-9 在每一行只能出现一次。
数字 1-9 在每一列只能出现一次。
数字 1-9 在每一个以粗实线分隔的 3x3 宫内只能出现一次。(请参考示例图)
数独部分空格内已填入了数字,空白格用 ‘.’ 表示。

注意:

一个有效的数独(部分已被填充)不一定是可解的。
只需要根据以上规则,验证已经填入的数字是否有效即可。

示例 1:

输入:board =
[[“5”,“3”,".",".",“7”,".",".",".","."]
,[“6”,".",".",“1”,“9”,“5”,".",".","."]
,[".",“9”,“8”,".",".",".",".",“6”,"."]
,[“8”,".",".",".",“6”,".",".",".",“3”]
,[“4”,".",".",“8”,".",“3”,".",".",“1”]
,[“7”,".",".",".",“2”,".",".",".",“6”]
,[".",“6”,".",".",".",".",“2”,“8”,"."]
,[".",".",".",“4”,“1”,“9”,".",".",“5”]
,[".",".",".",".",“8”,".",".",“7”,“9”]]
输出:true

作者:力扣 (LeetCode)
链接:https://leetcode-cn.com/leetbook/read/top-interview-questions-easy/x2f9gg/
来源:力扣(LeetCode)

利用三个二维数组去存放行,列,九宫格,通过值的大小进行存放位置。

class Solution {
    public boolean isValidSudoku(char[][] board) {
            int length = board.length;
            int[][] line = new int[length][length];
            int[][] colunm = new int[length][length];
            int[][] cell = new int[length][length];

            for(int i = 0 ; i < length; i++){
                for(int j = 0 ; j < length; j++){
                    if(board[i][j] == '.'){
                        continue;
                    }
                    int num = board[i][j] - '0' - 1;
                    int k = i / 3 * 3 + j / 3;

                    if(line[i][num] != 0 || colunm[j][num] != 0 || cell[k][num] != 0){
                        return false;
                    }

                    line[i][num] = colunm[j][num] = cell[k][num] = 1;
                }
            }
            return true;
    }
}

不懂的是
int num = board[i][j] - ‘0’ - 1;
int k = i / 3 * 3 + j / 3; 确定九宫格的位数

旋转图像
旋转图像
给定一个 n × n 的二维矩阵 matrix 表示一个图像。请你将图像顺时针旋转 90 度。

你必须在 原地 旋转图像,这意味着你需要直接修改输入的二维矩阵。请不要 使用另一个矩阵来旋转图像。

示例 1:

输入:matrix = [[1,2,3],[4,5,6],[7,8,9]]
输出:[[7,4,1],[8,5,2],[9,6,3]]
示例 2:

输入:matrix = [[5,1,9,11],[2,4,8,10],[13,3,6,7],[15,14,12,16]]
输出:[[15,13,2,5],[14,3,4,1],[12,6,8,9],[16,7,10,11]]

作者:力扣 (LeetCode)
链接:https://leetcode-cn.com/leetbook/read/top-interview-questions-easy/xnhhkv/
来源:力扣(LeetCode)

lass Solution {
    public void rotate(int[][] matrix) {
        int length = matrix.length;
        for(int i = 0 ; i < length / 2 ; i++){
            int[] temp = new int[length];
            temp = matrix[i];
            matrix[i] = matrix[length - i - 1];
            matrix[length - i - 1] = temp;
        }

        for(int i = 0 ; i < length ; i++){
            for(int j = i + 1  ; j < length ; j++){
                int temp = matrix[i][j];
                matrix[i][j] = matrix[j][i];
                matrix[j][i] = temp;

            }
        }
    }
}

先首行和尾行进行反转,再对角线替换,总体而言,更重要的是需要查看题目的规律,主要还是找不到突破点。
或者从外圈到内圈里面慢慢遍历。

给定一个字符串,验证它是否是回文串,只考虑字母和数字字符,可以忽略字母的大小写。

示例 1:

输入: “A man, a plan, a canal: Panama”
输出: true
示例 2:

输入: “race a car”
输出: false

使用双指针的方法,从左从右判断,先去除非字符串,可以使用正则或者Chacater.isLetterOrDigit()方法判断

class Solution {
    public boolean isPalindrome(String s) {
        // 不会转小写
        s = s.toLowerCase();
        int j = s.length() - 1;
        int i = 0 ;
        while(i < j){
            // 没有进行多个遍历
            while(i < j && !Character.isLetterOrDigit(s.charAt(j))){
                  j--;
            }
                
            while(i < j && !Character.isLetterOrDigit(s.charAt(i))){
                 i++;
            }
           
            if(s.charAt(i) != s.charAt(j)){
                return false;
            }
            i ++ ;
            j -- ;
        }
        return true;
    }
    // 判断字符串里的字符是否会是火星文
    // public boolean judege (char start){
    //     if(start == ',' || start == ':' || start == ' ' || start == '.' || start == '@'){
    //         return true;
    //     }else{
    //         return false;
    //     }
    // }
}

外观数列

输入:n = 4
输出:“1211”
解释:
countAndSay(1) = “1”
countAndSay(2) = 读 “1” = 一 个 1 = “11”
countAndSay(3) = 读 “11” = 二 个 1 = “21”
countAndSay(4) = 读 “21” = 一 个 2 + 一 个 1 = “12” + “11” = “1211”

class Solution {
    public String countAndSay(int n) {
        if(n == 1){
            return "1";
        }
        int count = 0;
        String belen = countAndSay(n-1);
        char local = belen.charAt(0);
        StringBuilder res = new StringBuilder();
        for(int i = 0 ; i < belen.length() ; i++){
            if(belen.charAt(i) == local){
                count ++ ;
            }else{
                res.append(count);
                res.append(local);
                local = belen.charAt(i);
                count = 1;
            }
        }
        res.append(count);
        res.append(local);
        return res.toString();
    }
}

采用计数到遍历。
总结,可以采用画图法来探讨。

删除链表倒数n个节点

/**
 * 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 removeNthFromEnd(ListNode head, int n) {
        ListNode node = head;
        int lengt = 0;
        // 计数
        while(node != null){
            lengt ++ ;
            node = node.next;
        }
        int dingwei = lengt - n ;
        if(dingwei == 0){
            return head.next;
        }
        // 保留前一个链表
        ListNode nodes = head;
        for(int i = 0 ; i < dingwei -1 ; i++){
            nodes = nodes.next;
        }
        nodes.next = nodes.next.next;
        return head;
    }
}

反转链表

/**
 * 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( null == head || null == head.next ){
            return head;
        }
        ListNode node = head.next;
        ListNode pre = head;
        int count = 0;
        while(node.next != null){
            node = node.next;
            pre = node;
            count ++ ;
        }
        ListNode pres = reverseTwo(pre,node);
        if(count == 0){
            return node;
        }
        count ++;
        while(count != 0 ){
            ListNode prea = head;
            while(prea.next != pres ){
                prea = prea.next;
            }
            pres = reverseTwo(prea,pres);
            count -- ;
        }

        return node;
    }

    public ListNode reverseTwo(ListNode pre,ListNode now){
        now.next = pre;
        pre.next = null;
        return pre;
    }
}

通过先反转倒数两个,然后遍历每次从头节点到pre节点进行转换。利用count计数器计数。
其余方法
利用栈
利用双链表,每一次进行插入头节点。

回文链表

请判断一个链表是否为回文链表。

/**
 * 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 boolean isPalindrome(ListNode head) {
        Stack<Integer> stack = new Stack<Integer>();
        ListNode node = head;
        while(node != null){
            stack.push(node.val);
            node = node.next;
        }
        while(head.next != null){
            if(stack.pop() == head.val){
                head = head.next;
            }else{
                return false;
            }
        }
        return true;
    }
}

环形链表

给定一个链表,判断链表中是否有环。

如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,我们使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。 如果 pos 是 -1,则在该链表中没有环。注意:pos 不作为参数进行传递,仅仅是为了标识链表的实际情况。

如果链表中存在环,则返回 true 。 否则,返回 false 。

采用集合的算法。

/**
 * Definition for singly-linked list.
 * class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) {
 *         val = x;
 *         next = null;
 *     }
 * }
 */
public class Solution {
    public boolean hasCycle(ListNode head) {
        if(head == null){
            return false;
        }
        ListNode node = head;
        Set<ListNode> set = new HashSet<>();
        while(node.next != null){
           if(!set.add(node)){
               return true;
           }
              node = node.next;
        }
        return false;
    }
}

采用快慢指针
就像钟一样,对于时针,秒来说,快慢环形,会达到接触,只要判断两个快慢指针是否能够相等即可。

/**
 * Definition for singly-linked list.
 * class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) {
 *         val = x;
 *         next = null;
 *     }
 * }
 */
public class Solution {
    public boolean hasCycle(ListNode head) {
        if(head == null){
            return false;
        }
        ListNode fast = head;
        ListNode slow = head;
        while(fast.next != null && fast.next.next != null){
           fast = fast.next.next;
           slow = slow.next;
           if(fast == slow){
               return true;
           }
        }
        return false;
    }
}

二叉树的最大深度
给定一个二叉树,找出其最大深度。

二叉树的深度为根节点到最远叶子节点的最长路径上的节点数。

说明: 叶子节点是指没有子节点的节点。

示例:
给定二叉树 [3,9,20,null,null,15,7],

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode() {}
 *     TreeNode(int val) { this.val = val; }
 *     TreeNode(int val, TreeNode left, TreeNode right) {
 *         this.val = val;
 *         this.left = left;
 *         this.right = right;
 *     }
 * }
 */
class Solution {
    public int maxDepth(TreeNode root) {
         
            return root==null? 0 : Math.max(maxDepth(root.left), maxDepth(root.right))+1;
           
    }
}

验证二叉搜索树

验证二叉搜索树
给定一个二叉树,判断其是否是一个有效的二叉搜索树。

假设一个二叉搜索树具有如下特征:

节点的左子树只包含小于当前节点的数。
节点的右子树只包含大于当前节点的数。
所有左子树和右子树自身必须也是二叉搜索树。

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode() {}
 *     TreeNode(int val) { this.val = val; }
 *     TreeNode(int val, TreeNode left, TreeNode right) {
 *         this.val = val;
 *         this.left = left;
 *         this.right = right;
 *     }
 * }
 */
class Solution {
    public boolean isValidBST(TreeNode root) {
      return  isValidBST(root,Long.MIN_VALUE,Long.MAX_VALUE);
    }

    public boolean isValidBST(TreeNode root, long minVal,long maxVal){
        if(root == null ){
            return true;
        }
        if(root.val <= minVal || root.val >= maxVal){
            return false;
        }
        return isValidBST(root.left,minVal,root.val) && isValidBST(root.right, root.val,maxVal);
    }
}

你可能感兴趣的:(算法,算法)