ベクトル
整数を保持する可変長配列A={a0,a1,...}に対して、以下の操作を行ってください。
-
pushBack(x): Aの末尾に整数xを挿入する
-
randomAccess(p):Aの要素apの値を出力する
-
popBack(): Aの末尾の要素を削除する
Aは0-オリジンでインデックスが付けられ、初期状態では空とします。 -
input
入力形式は以下の形で与えられる
q
query1
query2
:
queryq
また、各クエリは
0 x
または1 p
または2
の形式で与えられます。最初の数字0, 1, 2 は操作の種類を示し、それぞれpushBack、randomAccess、popBackを表します。空の配列に対して、randomAccess や popBack操作が行われることはありません -
output
各randomAccess操作ごとに、apの値を1行に出力してください
import java.io.*;
import java.util.ArrayList;
public class Main{
public static void main(String[]args) throws IOException{
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
int q = Integer.parseInt(br.readLine());
//可変長配列A
ArrayList<Integer>A= new ArrayList<Integer>();
//命令分割
for(int i=0;i<q;i++){
//String型配列に命令を格納→IntegerParseInt忘れずに!
String[] arr = br.readLine().split(" ");
int op = Integer.parseInt(arr[0]);
switch(op){
case 0:
int x = Integer.parseInt(arr[1]);
A.add(x);
break;
case 1:
int p = Integer.parseInt(arr[1]);
System.out.println(A.get(p));
break;
case 2:
A.remove(A.size()-1);
break;
}
}
}
}
- throws IOException は、そのメソッド(例:main)の中で、IOException(入出力例外)が発生する可能性があることを Java に伝えるための宣言
- 「そのエラーが起きたら、呼び出し元(またはOS)に処理を任せるよ!」という意味
- 入力が大きいため、標準入力の高速化を図る→鉄板は以下
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
int q = Integer.parseInt(br.readLine());
- BufferedReader→ユーザーのキーボード入力(標準入力)を、「文字列」として1行ずつ読み込めるようにする→入力値で演算等行いたい場合はInteger.parseInt
- ArrayListの要素を取り出す→list.get(インデックス) で要素を取得
- Java の ArrayList は、要素に順番がある可変長配列
→インデックスは 0から始まり、末尾のインデックスは size() - 1 - 今回はArrayListを使ったが似ているLinkedListもおさえておくとGOOD
- LikedList→要素へのインデックスアクセスや要素の途中挿入・削除は遅いが末尾&先頭操作ならArrayListより得意で速い!
- 要素を ランダムにアクセス(インデックス指定)したい場合はArrayList
- 要素の 追加・削除が頻繁(特に先頭)ならLinkedList
デキュー
整数を保持する可変長配列A={a0,a1,...}に対して、以下の操作を行ってください。
- push(d,x): dが0の場合、Aの先頭に整数xを挿入する。dが1の場合、末尾にxを挿入する
- randomAccess(p):Aの要素apの値を出力する
- pop(): dが0の場合、Aの先頭の要素を削除する。dが1の場合、末尾の要素を削除する
Aは0-オリジンでインデックスが付けられ、初期状態では空とします - input
入力形式は以下の形で与えられる
q
query1
query2
:
queryq
また、各クエリは
0 d x
または1 p
または2 d
の形式で与えられます。最初の数字0, 1, 2 は操作の種類を示し、それぞれpushBack、randomAccess、popBackを表します。空の配列に対して、randomAccess や popBack操作が行われることはありません - output
各randomAccess操作ごとに、apの値を1行に出力してください
import java.io.*;
public class Main {
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
StringBuilder sb = new StringBuilder();
// 巨大な配列に対応するため十分なサイズで初期化
int maxSize = 800000; // 最大操作数の2倍程度
int[] deque = new int[maxSize];
int front = 400000; // 中央から始める
int rear = 400000; // 空のデックを表す(front == rear)
int q = Integer.parseInt(br.readLine());
for (int i = 0; i < q; i++) {
String[] arr = br.readLine().split(" ");
int op = Integer.parseInt(arr[0]);
switch (op) {
case 0: // push
int d1 = Integer.parseInt(arr[1]);
int x = Integer.parseInt(arr[2]);
if (d1 == 0) {
// 先頭に要素を追加
front--;
//そこに値xを格納
deque[front] = x;
} else {
// 末尾に追加
deque[rear] = x;
rear++;
}
break;
case 1: // randomAccess
int p = Integer.parseInt(arr[1]);
sb.append(deque[front + p]).append("\n");
break;
case 2: // pop
int d2 = Integer.parseInt(arr[1]);
if (d2 == 0) {
// 先頭インデックスを1つ進めて、デックの先頭要素を削除
front++;
} else {
// 末尾から削除
rear--;
}
break;
}
}
System.out.print(sb);
}
}
- 配列を使って二重端のデータ構造(デック)を実現している。主に、push、randomAccess、pop の操作を効率的に処理する
- 入力→BufferedReaderで受け取る
- デキューでは、データの先頭(front)と末尾(rear)の両方で、データの追加(push)や削除(pop)ができる
→データの挿入と削除が両端で行えるため、普通のキューよりも柔軟にデータを処理できる
// 先頭にデータを追加
deque[front] = x;
front--; // front を減らして、次に追加する位置を前に移動
// 末尾にデータを追加
deque[rear] = x;
rear++; // rear を増やして、次に追加する位置を後ろに移動
- front はデックの先頭の位置を指すので、front + p は配列内の実際の位置を計算している
- randomAccess() は「出力内容を準備する」だけのメソッドで、実際の「出力」は main メソッドの最後にまとめて行っている
- スコープについて理解が甘かったのでまとめておく
・同じブロックの中で同じ名前の変数は使えない
→一度定義したら、そのブロックの中では再定義できない
・{} ブロックの中で宣言された変数は、そのブロックの中でしか使えない
・ブロックが違えば、同じ名前でも別の変数として使える
→例えばswitch文では
switch (op) {
case 0: {
int d = Integer.parseInt(arr[1]);
// d を使う処理
break;
}
case 2: {
int d = Integer.parseInt(arr[1]);
// d を使う処理
break;
}
}
このようにcaseごとにブロックでかこめばどちらのケースでもdを定義して使える
別解
import java.io.IOException;
import java.io.InputStream;
import java.util.NoSuchElementException;
public class Main{
public static void main(String[] args) {
//高速入力クラス `FastScanner` を使って、最初にクエリ数 `q` を読み取る
FastScanner fs = new FastScanner();
int q = fs.nextInt();
//MyQueue` という自作のデックを長さ `q`(クエリ数)に基づいて初期化
MyQueue qu = new MyQueue(q);
for(int i = 0; i < q; i++) {
int query = fs.nextInt();
if(query == 0) {
int d = fs.nextInt();
int v = fs.nextInt();
if(d == 0) {
qu.addFirst(v);;
}else {
qu.addLast(v);
}
}else if(query == 1) {
System.out.println(qu.get(fs.nextInt()));
}else if(query == 2){
int d = fs.nextInt();
if(d == 0) {
qu.removeFirst();;
}else {
qu.removeLast();
}
}
}
}
// 両端キューの自作クラス
static class MyQueue {
//first: 先頭要素の直前の位置
//last: 末尾要素の直後の位置
public int[] array;
public int first, last;
public MyQueue (int n) {
//中央からスタートすることで、前にも後ろにも挿入できるようにしている(first と last が同じ=空の状態)
array = new int[n * 2];
this.first = n;
this.last = n;
}
void addFirst(int i) {
//要素を前に追加 → first を左にずらす
array[first--] = i;
}
void addLast(int i) {
array[++last] = i;
}
void removeFirst() {
first++;
}
void removeLast() {
last--;
}
int get(int n) {
return array[n + first + 1];
}
}
// 高速入力クラス
static class FastScanner {
private final InputStream in = System.in;
private final byte[] buffer = new byte[1024];
private int ptr = 0;
private int buflen = 0;
private boolean hasNextByte() {
if (ptr < buflen) {
return true;
}else{
ptr = 0;
try {
buflen = in.read(buffer);
} catch (IOException e) {
e.printStackTrace();
}
if (buflen <= 0) {
return false;
}
}
return true;
}
private int readByte() { if (hasNextByte()) return buffer[ptr++]; else return -1;}
private static boolean isPrintableChar(int c) { return 33 <= c && c <= 126;}
public boolean hasNext() { while(hasNextByte() && !isPrintableChar(buffer[ptr])) ptr++; return hasNextByte();}
public String next() {
if (!hasNext()) throw new NoSuchElementException();
StringBuilder sb = new StringBuilder();
int b = readByte();
while(isPrintableChar(b)) {
sb.appendCodePoint(b);
b = readByte();
}
return sb.toString();
}
public long nextLong() {
if (!hasNext()) throw new NoSuchElementException();
long n = 0;
boolean minus = false;
int b = readByte();
if (b == '-') {
minus = true;
b = readByte();
}
if (b < '0' || '9' < b) {
throw new NumberFormatException();
}
while(true){
if ('0' <= b && b <= '9') {
n *= 10;
n += b - '0';
}else if(b == -1 || !isPrintableChar(b)){
return minus ? -n : n;
}else{
throw new NumberFormatException();
}
b = readByte();
}
}
public int nextInt() {
long nl = nextLong();
if (nl < Integer.MIN_VALUE || nl > Integer.MAX_VALUE) throw new NumberFormatException();
return (int) nl;
}
public double nextDouble() { return Double.parseDouble(next());}
}
}
- FastScanner クラス
→標準入力を高速に処理するための自作クラス 。標準の Scanner よりずっと速くなるので、競技プログラミングでよく使われる。全部覚える必要はないが理解しておくとカスタムできるのでGOOD
ただし標準ライブラリでないことからimportできない、読むのが大変なコードなので保守性・可読性が低いことからチーム開発など競プロ系以外では使わないほうがいい
リスト
整数を保持するリストLに対して、以下の操作を行ってください。Lにはその末尾を表すEND要素が常に存在し、各要素はカーソルによって指定され操作されます
- insert(x): カーソルが指す要素の直前に整数xを挿入する。操作の後、カーソルは挿入された要素を指すようになる
- move(d): 整数dが正の場合、カーソルを後方にdだけ移動する。負の場合は前方にdだけ移動する。
- erase(): カーソルが指す要素を削除する。操作の後、カーソルは削除された要素の1つ後方の要素を指すようになる。ただし、そのような要素がない場合はENDを指すようになる。初期状態でLは空であり、カーソルはENDを指しているものとします
import java.io.*;
import java.util.*;
public class Main {
static LinkedList<Integer> list = new LinkedList<>();
static ListIterator<Integer> cursor;
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
// ENDを最初に追加
list.add(1000000001); // 仮のEND
//LinkedList に対して「リスト内を移動できるカーソル(反復子)」を作成
cursor = list.listIterator(); // 最初はENDを指す
int q = Integer.parseInt(br.readLine());
for (int i = 0; i < q; i++) {
String[] parts = br.readLine().split(" ");
int op = Integer.parseInt(parts[0]);
if (op == 0) { // insert
int x = Integer.parseInt(parts[1]);
insert(x);
} else if (op == 1) { // move
int d = Integer.parseInt(parts[1]);
move(d);
} else if (op == 2) { // erase
erase();
}
}
// ENDを除いて出力
for (int n : list) {
if (n != 1000000001) {
System.out.println(n);
}
}
}
static void insert(int x) {
cursor.add(x); // カーソルの前に挿入
cursor.previous(); // 挿入された要素を指す
}
static void move(int d) {
while (d > 0 && cursor.hasNext()) {
cursor.next();
d--;
}
while (d < 0 && cursor.hasPrevious()) {
cursor.previous();
d++;
}
}
static void erase() {
if (!cursor.hasNext()) return; // ENDを指しているなら何もしない
cursor.next(); // 要素を取得
cursor.remove(); // それを削除
// 自動的に次の要素を指す(なければEND)
}
}
- Integer.parseInt() の引数の型は StringなのでreadLine().split(" ");(=String[])を丸々Integer.parseIntしようとするとエラー
- LinkedList を使って整数リストを管理する
- ListIterator で「カーソル(現在位置)」を表す
初期状態
LinkedList<Integer> list = new LinkedList<>();
ListIterator<Integer> cursor = list.listIterator();
このとき
カーソルはリストの先頭(空の位置):
↓
[]
cursor.add(5);
[5]
↑
カーソルは「5」の**後ろ**にいる状態
cursor.add(10);
10 をカーソル位置に挿入
カーソルは 10 の後ろに移動
状態:
[5, 10]
↑
カーソルは 10 の後ろを指している
cursor.previous();
カーソルを 1つ前に戻す
状態:
[5, 10]
↑
カーソルは 10 の前を指している
cursor.next();
カーソルを 1つ後ろに進める
今、カーソルは再び 10 の後ろ
状態:
[5, 10]
↑
カーソルは 10 の後ろを指している
- move()→例えばd=2のときはd が 2 → 1 → 0 となるまでループする=カーソルを2回後ろに動かすことに相当
ベクトル②
整数を保持するn個の可変長配列Ai(i=0,1,...,n−1)に対して、以下の操作を行ってください。
- pushBack(t, x): Atの末尾に整数xを挿入する
- dump(t): Atの全ての要素を出力する
- clear(t): Atを空にする。ただし、Atが空の場合は何もしない
Aiは0-オリジンでインデックスが付けられ、初期状態では空とします。
import java.io.*;
import java.util.ArrayList; //インデックスアクセスなので可変長配列としてはArrayListを用いる
import java.util.List;
public class Main{
static List<List<Integer>> A; //Aはリストのリスト
public static void main(String[]args)throws IOException{
BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); //文字列として読み取る
String[] firstLine = br.readLine().split(" ");
int n = Integer.parseInt(firstLine[0]);
int q = Integer.parseInt(firstLine[1]);
A = new ArrayList<>(); // ← ここで n を使えるように main で初期化
// 各リストを初期化
for (int i = 0; i < n; i++) {
A.add(new ArrayList<>());
}
//q回クエリを読み取る
for(int i=0;i<q;i++){
String[] arr =br.readLine().split(" ");//クエリをarrの各要素に分割
int op =Integer.parseInt(arr[0]);
if(op==0){
int t = Integer.parseInt(arr[1]);
int x = Integer.parseInt(arr[2]);
pushBack(t,x);
}else if(op==1){
int t = Integer.parseInt(arr[1]);
dump(t);
}else{
int t = Integer.parseInt(arr[1]);
clear(t);
}
}
}
public static void pushBack(int t,int x){
List<Integer> list = A.get(t);//Aのt番目のリスト{}
list.add(x);
}
public static void dump(int t){
List<Integer> list = A.get(t);
for (int i = 0; i < list.size(); i++) {
if (i > 0) {
System.out.print(" ");
}
System.out.print(list.get(i));
}
System.out.println(); // 改行(空でも改行が必要)
}
public static void clear(int t){
List<Integer> list = A.get(t);
list.clear();
}
}
- Aはリストのリスト
- A = new ArrayList<>();とするとA は例えば次のような構造に
A = [
[1, 2, 3],
[4, 5],
[],
[6]
];
ArrayListやLinkedListのような実装に依存する設計よりListのようなインターフェースに依存する設計のほうがい