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+矩形面積)的時間,你可以渲染一大塊矩形把其中多個矩形一起渲染,你要儘可能使時間最小。如果你還看不懂就看下面這個圖:
黑色的矩形代表髒矩形 紅色的矩形代表渲染的矩形 因爲左邊矩形的重合較多 所以可以一起渲染使得花費時間更少 右邊的矩形因爲離左邊過遠所以不適合一起渲染
這個問題需要考慮兩種情況:
- 兩個矩形相交 考慮合併 合併花費大於非合併--》不合並 否則合併
- 兩個矩形距離較近 考慮合併 合併花費大於非合併--》不合並 否則合併
那麼整個解題的流程就出來了
處理輸入--》矩形數組
遍歷矩形{
如果相交且合併消耗小於等於非合併{
合併矩形
}
}
遍歷矩形{
如何兩者合併消耗小於非合併{
合併矩形
}
}
遍歷矩形{
計算消耗
}
合併矩形很簡單,左上角的哪個小要哪個,右下角的哪個大要哪個
主要問題在判別兩個矩形是否相交上,如果兩個矩形相交,那麼肯定一個矩形的點在另一個矩形的邊上或者裏面
可以相互判斷是否有訂單符合這個條件,使用或運算符相連,只要有一個爲真就是相交
那這種方法效率肯定比較低,寫起來也比較麻煩,還有一種數學方法
如果寬左移左頂點橫座標或者右移另一個左頂點的橫座標爲真的話那麼就說明左頂點相交,四個頂點依次判斷
因爲位運算比較快,所以效率較高
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");
}
}
}