【精选】JAVA算法题(十六)

这篇博客介绍了三道JAVA算法题:1.脏矩形合并,通过位运算优化判断相交的效率;2.反转链表,包括栈、双指针和递归三种解法;3.博弈问题,分析两人博弈策略以确定获胜者。通过这些题目,深入理解JAVA算法应用和博弈论思想。

1.脏矩形合并

题目:

 在2D渲染系统中,局部渲染是常见提升渲染性能的方法。如果界面中有元素发生了改变,我们可以将这个元素所占矩形区域标记为脏矩形,那么在接下来的渲染中,我们仅对每个脏矩形所占矩形区域执行一次局部渲染即可,无需渲染全屏。
 但系统提供的局部渲染API有如下限制:
 1. 单次局部渲染区域必需是矩形,不能是多边形,圆形或者其他不规则形状。
 2. 单次局部渲染时间开销除了和渲染像素数量呈线性正相关之外,还有一些固定的额外开销。
 3. 单个元素不可切分渲染,既最初标记的脏矩形不可切分渲染。
 因此为了总渲染时间开销最优,我们一般不直接对每个标记的脏矩形都执行一次局部渲染,而是先对重叠或者相近的脏矩形进行合并,从而减少局部渲染次数。
 经过测试,在某台设备上,单次局部渲染时间开销与渲染像素数量(既渲染面积)的关系为f(x)=10000+x。
 现在要求你设计一个算法,算法输入一组脏矩形列表,输出经过合并后的脏矩形列表的总渲染时间开销,要求合并后的脏矩形列表总渲染时间开销在这台设备上最优。
 输入:
 输入数据包含多行
 第1行,整数N(脏矩形数量,1<=N<=8)
 第2行,整数L1(第一个脏矩形左上角横坐标)
 第3行,整数T1(第一个脏矩形左上角纵坐标)
 第4行,整数W1(第一个脏矩形宽, 1<=W1)
 第5行,整数H1(第一个脏矩形高, 1<=H1)
 ...
 第4*(N-1)+2行,整数LN(第N个脏矩形左上角横坐标)
 第4*(N-1)+3行,整数TN(第N个脏矩形左上角纵坐标)
 第4*(N-1)+4行,整数WN(第N个脏矩形宽, 1<=WN)
 第4*(N-1)+5行,整数HN(第N个脏矩形高, 1<=HN)
 输出:
 输出合并后的脏矩形列表总渲染时间开销
 输入范例:
 5
 232
 66
 111
 41
 197
 44
 29
 53
 154
 208
 42
 12
 177
 87
 9
 102
 75
 168
 79
 41
 输出范例:
 45291

这是我前两天参加阿里春招笔试的第一道编程题,可能读起来确实有些让人费解,大体的意思就是给你若干个矩形的坐标,你要渲染这些矩形,每执行一次渲染会消耗(10000+矩形面积)的时间,你可以渲染一大块矩形把其中多个矩形一起渲染,你要尽可能使时间最小。如果你还看不懂就看下面这个图:

黑色的矩形代表脏矩形 红色的矩形代表渲染的矩形 因为左边矩形的重合较多 所以可以一起渲染使得花费时间更少 右边的矩形因为离左边过远所以不适合一起渲染

这个问题需要考虑两种情况:

  1. 两个矩形相交 考虑合并 合并花费大于非合并--》不合并 否则合并
  2. 两个矩形距离较近 考虑合并 合并花费大于非合并--》不合并 否则合并

那么整个解题的流程就出来了

处理输入--》矩形数组
遍历矩形{
    如果相交且合并消耗小于等于非合并{
        合并矩形
    }
}
遍历矩形{
    如何两者合并消耗小于非合并{
        合并矩形
    }
}
遍历矩形{
    计算消耗
}

合并矩形很简单,左上角的哪个小要哪个,右下角的哪个大要哪个

主要问题在判别两个矩形是否相交上,如果两个矩形相交,那么肯定一个矩形的点在另一个矩形的边上或者里面

可以相互判断是否有订单符合这个条件,使用或运算符相连,只要有一个为真就是相交

那这种方法效率肯定比较低,写起来也比较麻烦,还有一种数学方法

如果宽左移左顶点横坐标或者右移另一个左顶点的横坐标为真的话那么就说明左顶点相交,四个顶点依次判断

因为位运算比较快,所以效率较高

    private static class Rect {
        private int left;
        private int top;
        private int width;
        private int height;
    }

    public static void main(String[] args) {
        Scanner in = new Scanner(System.in);
        int n = Integer.parseInt(in.nextLine());
        Rect[] rects = new Rect[n];
        for (int i = 0; i < n; i++) {
            rects[i] = new Rect();
            rects[i].left = Integer.parseInt(in.nextLine());
            rects[i].top = Integer.parseInt(in.nextLine());
            rects[i].width = Integer.parseInt(in.nextLine());
            rects[i].height = Integer.parseInt(in.nextLine());
        }
        System.out.println(String.valueOf(costTime(rects)));
    }

    private static long costTime(Rect[] rects) {
        long count=0;
        int[] merge=new int[rects.length];
        //合并相交
        for (int i=0;i<rects.length-1;i++){
            for (int j=i+1;j<rects.length;j++){
                if (isIntersect(rects[i],rects[j])){
                    Rect rect=creatRect(i,j,rects);
                    if (consume(rect)>consume(rects[i])+consume(rects[j])){
                        //合并矩形花费高于不合并
                        continue;
                    }else {
                        //合并矩形花费低于等于不合并
                        rects[i]=rect;
                        merge[j]=1;
                    }
                }
            }
        }
        //合并非相交
        for (int i=0;i<rects.length-1;i++) {
            if (merge[i]==1){
                continue;
            }
            for (int j = i + 1; j < rects.length; j++) {
                if (merge[j]==1){
                    continue;
                }
                if (isIntersect(rects[i],rects[j])){
                    Rect rect=creatRect(i,j,rects);
                    if (consume(rect)>consume(rects[i])+consume(rects[j])){
                        //合并矩形花费高于不合并
                        continue;
                    }else {
                        //合并矩形花费低于等于不合并
                        rects[i]=rect;
                        merge[j]=1;
                    }
                }
            }
        }
        for (int i=0;i<rects.length;i++){
            if (merge[i]==1){
                continue;
            }
            count+=consume(rects[i]);
        }

        return count;
    }

    private static Rect creatRect(int i,int j,Rect[] rects){
        int x1=rects[i].left<rects[j].left?rects[i].left:rects[j].left;
        int y1=rects[i].top<rects[j].top?rects[i].top:rects[j].top;
        int x2=rects[i].left+rects[i].width>rects[j].left+rects[j].width?rects[i].left+rects[i].width:rects[j].left+rects[j].width;
        int y2=rects[i].top+rects[i].height>rects[j].top+rects[j].height?rects[i].top+rects[i].height:rects[j].top+rects[j].height;
        Rect rect=new Rect();
        rect.left=x1;
        rect.top=y1;
        rect.width=x2-x1;
        rect.height=y2-y1;
        return rect;
    }

    //花费
    private static long consume(Rect rect){
        return 10000+rect.width*rect.height;
    }
    //俩矩形是否相交
    private static boolean isIntersect(Rect rect1,Rect rect2){
        int tw = rect1.width;
        int th = rect1.height;
        int rw = rect2.width;
        int rh = rect2.height;
        if (rw <= 0 || rh <= 0 || tw <= 0 || th <= 0) {
            return false;
        }
        int tx = rect1.left;
        int ty = rect1.top;
        int rx = rect2.left;
        int ry = rect2.top;
        rw += rx;
        rh += ry;
        tw += tx;
        th += ty;
        return ((rw < rx || rw > tx) &&
                (rh < ry || rh > ty) &&
                (tw < tx || tw > rx) &&
                (th < ty || th > ry));
    }

2.反转链表

题目:

/**
 *反转一个单链表。
 示例:
 输入: 1->2->3->4->5->NULL
 输出: 5->4->3->2->1->NULL
 */
public class ListNode {
    int val;
    ListNode next;

    ListNode(int x) {
        val = x;
    }
}

 很常见的一道题,做法大致分为三种,最低效最容易想到的就是用栈,因为栈的特性后进先出所以可以使用辅助栈,先添加到栈中然后依次取出拼接,需要注意的就是要处理原链表的头结点,不要让它继续指向第二个节点,而应该指向null

    public static ListNode method3(ListNode head){
        if(head == null || head.next == null){
            return head;
        }
        Stack<ListNode> stack = new Stack<ListNode>();
        ListNode pre = null;
        while(head.next != null){
            stack.push(head);
            head = head.next;
        }
        pre = head;
        while(!stack.isEmpty()){
            head.next = stack.pop();
            head = head.next;
        }
        head.next = null;
        return pre;
    }

第二种做法就是用三个变量,前、中、后三个节点,不断的去分割链表中的节点然后指向前一个节点并遍历下一个节点

    public static ListNode method2(ListNode head){
        if (head == null) {
            return head;
        }
        ListNode pre = head;
        ListNode cur = head.next;
        ListNode next = null;
        while(cur != null){
            next = cur.next;
            cur.next = pre;
            pre = cur;
            cur = next;
        }
        head.next = null;
        head = pre;
        return head;
    }

第三种方法其实和第二种思想是一样的,核心思想就是分割节点,只是遍历组装的方式不同罢了,这里使用了递归

    public static ListNode method1(ListNode head) {
        if (head == null || head.next == null) return head;
        ListNode nextNode = head.next;
        head.next = null;
        ListNode reverseRest = method1(nextNode);
        nextNode.next = head;
        return reverseRest;
    }

3.博弈

题目:

两个人玩游戏,一个人叫Yui,一个叫Mio

游戏规则是这样的的,给定一个整数n,每次可以都从n上面减去1-m(m<n),谁先把n减到0谁就获胜

Yui先手,且每个人都会做最佳决定,那么谁会获胜。

输入:

第一行为测试数据组数,接下来每一行都有两个数字 第一个数字代表n,第二个数字代表m

输出:

获胜者的名字

这道题一看就是一道博弈论的问题,二人博弈,举个例子很容易就明白

比如n是17,m是4,A和B两个人玩,A先出手

A拿2,剩15   B拿1,剩14

A拿4,剩10   B拿3,剩7

A拿2,剩5     B拿2,剩3

A拿3,剩0,A赢

如果你还没发现规律,那就再来一个例子,还是A和B,n是24,m是4

A拿4,剩20   B拿2,剩18

A拿3,剩15   B拿4,剩11

A拿1,剩10   B拿3,剩7

A拿2,剩5    B拿3,剩2

A拿2,剩0,A赢

我们发现只要n对m+1取余不等于0,那么只要A第一次只要拿走余数,剩下的每轮只要拿(m+1减去B拿的数)就可以保证获胜

反之只要n对m+1取余等于0,B就可以利用这一规则获胜,那么这道题也就很好解了

public static void method1(){
        Scanner scanner=new Scanner(System.in);
        int count=scanner.nextInt();
        for (int i=0;i<count;i++){
            int n=scanner.nextInt();
            int m=scanner.nextInt();
            if (n%(m+1)==0){
                System.out.println("Mio");
            }else {
                System.out.println("Yui");
            }
        }
    }
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

幽蓝丶流月

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值