算法3

题目二十一 回文数

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

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

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

class Solution {
    public boolean isPalindrome(int x) {

        if(x<0){return false;}
        int t=x;
        int n=0;
        while(x!=0){
            n*=10;
            n+=x%10;
            x/=10;
        }
        if(n==t){return true;}else{return false;}
    }
}

算法很简单,只需要生成数的逆序与原来的数字比较即可

题目二十二 盛最多水的容器

给定一个长度为 n 的整数数组 height 。有 n 条垂线,第 i 条线的两个端点是 (i, 0)(i, height[i])

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

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

说明:你不能倾斜容器。

class Solution {
    public int maxArea(int[] height) {
        int k = 0;

        int i= 0,j = height.length-1;

        while(j>i){
            if(height[i]>=height[j]){
                int n = height[j] * (j-i);
                if(n>k){k=n;}
                j--;
                continue;
            }else{
                int n = height[i] *(j-i);
                if(n>k){k=n;}
                i++;
                continue;
            }
        }
        return k;
    }
}

此题目用到的是双指针法,通过不断移动指针来缩小问题规模

推导:容器大小与较小的指针以及指针之间的长度有关,因此我们在查找最大容器时,只需要缩小较小的指针即可缩小问题规模。

为什么移动较小的指针?

由于较大指针继续缩小范围,只会使得容器变小,因此较小的指针不会再作为容器的边界了,所以我们才移动较小的指针。

每次更新指针,都将得到的新容器大小与历史最大容器大小比较并更新。

题目二十三 有效的括号

给定一个只包括 ‘(‘,’)’,’{‘,’}’,’[‘,’]’ 的字符串 s ,判断字符串是否有效。

有效字符串需满足:

左括号必须用相同类型的右括号闭合。
左括号必须以正确的顺序闭合。
每个右括号都有一个对应的相同类型的左括号。

class Solution {
    public boolean isValid(String s) {

        ArrayList stack =new ArrayList();
        for(int i=0;i< s.length();i++){
            char c = s.charAt(i);
            if(c =='{'||c=='['||c=='('){
                stack.add(c);
            }else{
                if(stack.size() == 0){
                    return false;
                }
                char lc = (Character)stack.get(stack.size()-1);
                if(lc == '{'&& c=='}' || lc == '['&&c==']'||lc=='('&&c==')'){
                    stack.remove(stack.size()-1);
                }else{return false;}
            }
        }
        if(stack.size() == 0){return true;}
        return false;
    }

}

本题使用到了栈的思想,使用ArrayList来实现栈存储。

我们只需要遍历一次字符串,不断将符号入栈,当遇到后括号时,我们对最近一个前括号和当前后括号进行测试,如果满足条件则一起出栈,不满足条件则返回false。当遍历完字符串后,如果字符串满足条件,则栈中内容应该被全部弹出。也就是栈为空则返回true,否则返回false。

注意:当读取到后括号发现栈为空时,用常规的读取前一个括号进行测试的方法已经失效(取值越界),因此直接返回false

题目二十四 括号生成

class Solution22 {
    private List<String> strings = new ArrayList<>();

    private int l;

    private int n;

    public List<String> generateParenthesis(int n) {

        l = n*2;
        this.n = n;

        build("(",1,0);

        return strings;
    }

    //深度优先搜索
    private void build(String s,int ln,int rn) {
        if (s.length() >= l){
            strings.add(s);
            return;
        }
        //发现左括号数量小于右括号数量,则进行剪枝
        if (rn>ln){
            return;
        }
        //如果左括号数量小于n则继续搜索
        if (ln<n){
            build(s+"(",ln+1,rn);
        }
        //如果右括号数量小于n则继续搜索
        if (rn<n){
            build(s+")",ln,rn+1);
        }
    }
}

本题使用到了回溯法的思想,通过递归调用实现深度优先搜索,对题目解析可以发现我们要找到的是符合规范的所有括号序列,但是在搜索过程中很明显,当左括号数量只要小于右括号数量时,则不符合规则,进行剪枝,直接返回。

因此我们需要维护变量ln和rn来记录当前方法栈左括号和右括号数量

当字符串满足生成数量时则返回

题目二十五 三数之和

class Solution23 {
    public List<List<Integer>> threeSum(int[] nums) {

       Arrays.sort(nums);
       List<List<Integer>> ans = new ArrayList<>();

        for (int i = 0; i < nums.length; i++) {
            if (i != 0 && nums[i-1] == nums[i]){
                continue;
            }//如果和前一个重复,则不构建此元组
            int k = nums.length -1;
            int target = -nums[i];
            for (int j = i+1; j < nums.length; j++) {

                if (j != i+1 && nums[j-1] == nums[j]){
                    continue;
                }//如果和前一个重复,则不构建此元组

                //第三个指针在第二层循环内是不断递减的
                while (k > j && nums[k] + nums[j] > target){
                    k--;
                }
                //后续的序列都不满足条件
                if (k == j){
                    break;
                }
                //满足条件,构建元组
                if (nums[k] + nums[j] == target){
                    ArrayList<Integer> list = new ArrayList<>();
                    list.add(nums[i]);
                    list.add(nums[j]);
                    list.add(nums[k]);
                    ans.add(list);
                }

            }
        }

       return ans;
    }

}

本题需要考虑到3元组的唯一性,我们可以通过构造(a,b,c) 其中a<=b<=c的关系来保证元组唯一性

由于题目提供的是乱序数字,我们先对数组进行排列

通过三指针法来遍历,第一层指针遍历所有的a,当遇到后一个与前一个数字相同时,我们应当避免,否则会产生重复。

第二层指针同理需要避免。当我们确定了a和b时,只需要找到第三个c即可

注意由于a+b必然是不断变大的,因此当我们在a不变,b不断变大的过程,c应当也是不断缩小的,当c和b的指针相遇时,则代表此序列在之后的a+b+c只会是不断变大,如果此时a+b+c>0那么就打破第二层循环,继续寻找新序列

因此第三层循环应该和第二层循环同时进行

题目二十六 删除有序数组中的重复项

给你一个 升序排列 的数组 nums ,请你 原地 删除重复出现的元素,使每个元素 只出现一次 ,返回删除后数组的新长度。元素的 相对顺序 应该保持 一致 。

由于在某些语言中不能改变数组的长度,所以必须将结果放在数组nums的第一部分。更规范地说,如果在删除重复项之后有 k 个元素,那么 nums 的前 k 个元素应该保存最终结果。

将最终结果插入 nums 的前 k 个位置后返回 k 。

不要使用额外的空间,你必须在 原地 修改输入数组 并在使用 O(1) 额外空间的条件下完成。

class Solution {
    public int removeDuplicates(int[] nums) {

int s =nums[0];

int k =1;
for(int i=1;i <nums.length;i++){
    
    if(nums[i] != s){
        s = nums[i];
        nums[k++] = s;
    }else{
        continue;
        }
    }
    
    
    return k;
    }
}

由于题目提供的是已经排列好的顺序表,因此只需要维护一个int类型的s来记录前一个数字和当前数字是否重复即可,重复的跳过,不重复的更新k位置数值,并让k指针右移

题目二十七 按列翻转得到最大值等行数

给定 m x n 矩阵 matrix 。

你可以从中选出任意数量的列并翻转其上的 每个 单元格。(即翻转后,单元格的值从 0 变成 1,或者从 1 变为 0 。)

返回 经过一些翻转后,行与行之间所有值都相等的最大行数 。

class Solution {
    public int maxEqualRowsAfterFlips(int[][] matrix) {
        Map<String,Integer> status = new HashMap();
        int n = matrix.length;
        int m= matrix[0].length;
        StringBuilder key = new StringBuilder();
        //1开头反转
        for(int i =0;i <n; i++){
            key.setLength(0);
            if(matrix[i][0] ==1){
                for(int j=0;j<m;j++){
                    key.append((matrix[i][j] == 0)?1:0);
                }
            }else{
                for(int j=0;j<m;j++){
                    key.append(matrix[i][j]);
                }
            }
            Integer num = status.get(key.toString());
            if(num ==null){
               status.put(key.toString(),1);
            }else{
               status.put(key.toString(),num+1);
            }
        }
    Integer max = 0;
   
    for(Integer value : status.values()){
        if(value > max){
            max = value;
        }
    }
    return max;
    
 }
}

本题使用到了Hash表来存储相同性质序列的出现次数,依据题意,我们可以分析出当存在相同序列或互逆(0和1相反)的序列出现,则必定能通过相同的列翻转形成值相等的行。可以分析出相同性质的序列只有0或1开头的两种情况,因此我们将1开头情况的行先都转换为0开头同性质的行,我们只需要统计出矩阵中出现次数的最多的行即可。

题目二十八 八数码难

八数码问题也叫九宫问题,是人工智能中状态搜索中的经典问题,其中,该问题描述为:在3×3的棋盘,摆有八个棋子,每个棋子上标有1至8的某一数字,不同棋子上标的数字不相同。棋盘上还有一个空格,与空格相邻的棋子可以移到空格中。要求解决的问题是:给出一个初始状态和一个目标状态,找出一种从初始转变成目标状态的移动棋子步数最少的移动步骤。

package com.os467;

import java.util.*;

/**
 * -----------测试数据(有解)------------
 * 2,8,3,6,1,4,7,0,5
 * 1,2,3,4,5,6,7,8,0
 *
 * 0,1,2,3,4,5,6,7,8
 * 3,1,2,4,5,6,7,8,0
 *
 * 1,2,3,4,5,6,7,8,0
 * 0,8,7,6,5,4,3,2,1
 *
 * 0,1,2,3,4,5,6,7,8
 * 1,2,0,3,4,5,6,7,8
 *
 *-------------测试数据(无解)------------
 * 1,2,3,4,5,6,7,8,0
 * 1,2,3,4,5,6,8,7,0
 *
 */
public class NineNumber {

    public static void main(String[] args) {

        Table table = new Table();

        long start = System.currentTimeMillis();
        table.start();
        long end = System.currentTimeMillis();
        System.out.println("算法共计用时"+((double)(end - start)/(double)1000)+"秒");

    }

}

class Location{
    private int i;
    private int j;

    public Location(int i, int j) {
        this.i=i;
        this.j=j;
    }

    public int getI() {
        return i;
    }

    public void setI(int i) {
        this.i = i;
    }

    public int getJ() {
        return j;
    }

    public void setJ(int j) {
        this.j = j;
    }

    public List<Location> getNear() {
        List<Location> locations = new ArrayList<>();
        if (i-1 >= 0){
            Location up = new Location(i-1,j);
            locations.add(up);
        }
        if (j-1 >= 0){
            Location left = new Location(i,j-1);
            locations.add(left);
        }
        if (j+1 <= 2){
            Location right = new Location(i,j+1);
            locations.add(right);
        }
        if (i+1 <= 2){
            Location down = new Location(i+1,j);
            locations.add(down);
        }
        return locations;
    }
}

/**
 * 九宫表
 */
class Table {

    private static Comparator comparator = new Comparator<Table>() {
        @Override
        public int compare(Table o1, Table o2) {
            return o1.getFx() - o2.getFx();
        }
    };

    private static StringBuilder stringBuilder = new StringBuilder();

    private String memoryWay;

    /**
     * 3*3二维矩阵
     */
    private int[][] table = new int[3][3];

    //曼哈顿之和
    private int sumMHD;

    //遍历深度
    private int level;

    //哈希记忆表
    private static HashSet<String> hashSet = new HashSet<>();

    private static List<Table> tableList = new ArrayList<>();

    //终止状态
    private static int[][] table0 = new int[3][3];

    public Table(){
        level = 1;
        System.out.println("请输入九宫格初始状态(用逗号隔开 0代表空位):");
        Scanner scanner = new Scanner(System.in);
        String s = scanner.nextLine();
        System.out.println("请输入九宫格终止状态(用逗号隔开 0代表空位):");
        String s1 = scanner.nextLine();
        String[] numbers = s.split(",");
        String[] numbers1 = s1.split(",");
        fillTable(table,numbers);
        fillTable(table0,numbers1);
    }

    /**
     * 序列检测
     * @return
     */
    private boolean checkReachable() {
        int[] nums = new int[9];
        int[] nums0 = new int[9];
        //装换为整型
        for (int i = 0,k = 0; i < table.length; i++) {
            for (int j = 0; j < table[0].length; j++,k++) {
                nums[k] = table[i][j];
                nums0[k] = table0[i][j];
            }
        }
        //初始状态逆序x和终止状态逆序x0
        int x = 0, x0 = 0;
        //检查逆序
        for (int i = 0; i < nums.length; i++) {
            for (int j = i - 1; j >= 0; j--) {
                if (nums[j] > nums[i] && nums[i] != 0){
                    x++;
                }
                if (nums0[j]>nums0[i] && nums0[i] != 0){
                    x0++;
                }
            }
        }
        System.out.println("起始状态序列逆序数为"+x);
        System.out.println("终止状态序列逆序数为"+x0);
        if (x%2 != x0%2){
            System.out.println("排序奇偶性不同");
            //奇偶性不同
            return false;
        }
        System.out.println("排序奇偶性相同");
        return true;
    }

    public Table(int[][] table,int level) {
        this.level = level;
        for (int i = 0; i < table.length; i++) {
            for (int j = 0; j < table[0].length; j++) {
               this.table[i][j] = table[i][j];
            }
        }
    }

    public int getSumMHD() {
        return sumMHD;
    }

    public void setSumMHD(int sumMHD) {
        this.sumMHD = sumMHD;
    }

    public int getFx(){
        return level + sumMHD;
    }

    public int getLevel() {
        return level;
    }

    public void setLevel(int level) {
        this.level = level;
    }

    /**
     * 初始化表
     * @param table
     * @param numbers
     */
    private void fillTable(int[][] table, String[] numbers) {
        for (int i = 0,k = 0; i < 3; i++) {
            for (int j = 0; j < 3; j++,k++) {
                table[i][j] = Integer.parseInt(numbers[k]);
            }
        }
    }


    /**
     * 开始解决八数码难问题
     *
     *
     *
     */
    public void start() {
        if (!checkReachable()){
            System.out.println("无解!");
            return;
        }
        //计算初始曼哈顿距离
        sumMHD = calculateSumMHD(this, table0);
        //记录初始状态路径值
        memoryWay = getPrint();
        Table move = move(this);
        System.out.println(move.memoryWay);
    }

    /**
     * 开始移动九宫格
     * @param table
     * @return
     */
    private Table move(Table table) {
        Stack<Table> stack = new Stack<>();
        stack.add(table);
        while (!stack.isEmpty()){
            //弹出栈顶元素
            Table popTable = stack.pop();
            table = popTable;
            level = popTable.level;

            //需要先获取到0的位置
            Location zeroLocate = getZeroLocate(table.table);
            if (zeroLocate == null){
                throw new RuntimeException("没有空位置");
            }
            List<Location> near = zeroLocate.getNear();

            //0可以移动的位置得到新的状态(不进入重复状态)
            for (Location target : near) {
                //计算出移动后新的曼哈顿距离
                Table movedTable = getMovedTable(table.table, zeroLocate, target, level+1);
                int sumMHD = calculateSumMHD(movedTable, table0);
                movedTable.setSumMHD(sumMHD);
                movedTable.memoryWay = table.memoryWay + movedTable.getPrint();
                String only = movedTable.getOnly();
                if (!hashSet.contains(only)){
                    tableList.add(movedTable);
                    hashSet.add(only);
                }
            }

            //获取到下一步的移动状态列表,如果存在曼哈顿距离为0的表,则代表成功抵达终止状态
            for (int i = 0; i < tableList.size(); i++) {
                int sumMHD = tableList.get(i).getSumMHD();
                if (sumMHD == 0){
                    return tableList.get(i);
                }
            }

            //根据Fx挑选最小的进行搜索
            //默认第一个
            Table next = tableList.get(0);
            int fx = next.getFx();
            int index = 0;
            for (int i = 1; i < tableList.size(); i++) {
                int fx1 = tableList.get(i).getFx();
                if (fx1 < fx){
                    index = i;
                    fx = fx1;
                }
            }

            next = tableList.get(index);

            //从tableList中移除
            tableList.remove(index);

            //入栈
            stack.add(next);
        }
        return null;
    }

    private String getOnly(){
        for (int i = 0; i < table.length; i++) {
            for (int j = 0; j < table[0].length; j++) {
                stringBuilder.append(table[i][j]);
            }
        }
        String s = stringBuilder.toString();
        stringBuilder.setLength(0);
        return s;
    }

    private String getPrint() {

        stringBuilder.append("第");
        stringBuilder.append(level);
        stringBuilder.append("步,曼哈顿距离为");
        stringBuilder.append(getSumMHD());
        stringBuilder.append(",当前代价为");
        stringBuilder.append(getFx());
        stringBuilder.append("\n");
        for (int i = 0; i < table.length; i++) {
            for (int j = 0; j < table[0].length; j++) {
                stringBuilder.append(table[i][j]+"  ");
            }
            stringBuilder.append("\n");
        }
        stringBuilder.append("\n");

        String s = stringBuilder.toString();
        stringBuilder.setLength(0);
        return s;
    }

    private Location getZeroLocate(int[][] table) {
        return getTargetLocate(0,table);
    }

    /**
     * 计算曼哈顿距离之和
     * @param movedTable
     * @param table0 目标状态
     */
    private int calculateSumMHD(Table movedTable, int[][] table0) {
        int[][] table = movedTable.table;
        int sum = 0;
        for (int i = 0; i < table.length; i++) {
            for (int j = 0; j < table[0].length; j++) {
                int num = table[i][j];
                if (num != 0){
                    Location startLocate = getTargetLocate(num, table);
                    Location endLocate = getTargetLocate(num, table0);
                    int iL = Math.abs(startLocate.getI() - endLocate.getI());
                    int jL = Math.abs(startLocate.getJ() - endLocate.getJ());
                    sum += (iL + jL);
                }
            }
        }
        return sum;
    }

    private Table getMovedTable(int[][] table, Location zero, Location target, int level) {
        Table newTable = new Table(table,level);
        int i = zero.getI();
        int j = zero.getJ();
        int i1 = target.getI();
        int j1 = target.getJ();
        newTable.table[i][j] = newTable.table[i1][j1];
        newTable.table[i1][j1] = 0;
        return newTable;
    }

    private Location getTargetLocate(int num,int[][] table) {
        for (int i = 0; i < table.length; i++) {
            for (int j = 0; j < table[i].length; j++) {
                if (table[i][j] == num){
                    return new Location(i,j);
                }
            }
        }
        return null;
    }
}

使用了启发式函数A*算法 + 哈希集合降低了时间复杂度,A*会计算并挑出当前代价最小的分支进行遍历,而哈希集合防止了分支走入重复计算的状态

A*算法代价计算:当前遍历深度 + 当前状态的优劣程度(此问题由曼哈顿距离大小决定)


转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以邮件至 1300452403@qq.com

文章标题:算法3

字数:4.1k

本文作者:Os467

发布时间:2023-03-29, 16:07:12

最后更新:2023-05-26, 23:13:19

原始链接:https://os467.github.io/2023/03/29/%E7%AE%97%E6%B3%953/

版权声明: "署名-非商用-相同方式共享 4.0" 转载请保留原文链接及作者。

×

喜欢就点赞,疼爱就打赏